tr]:last:border-b-0',
+ className,
+ )}
+ {...props}
+ />
+ );
+}
+
+function TableRow({ className, ...props }) {
+ return (
+
+ );
+}
+
+function TableHead({ className, ...props }) {
+ return (
+ [role=checkbox]]:translate-y-[2px]',
+ className,
+ )}
+ {...props}
+ />
+ );
+}
+
+function TableCell({ className, ...props }) {
+ return (
+ [role=checkbox]]:translate-y-[2px]',
+ className,
+ )}
+ {...props}
+ />
+ );
+}
+
+function TableCaption({ className, ...props }) {
+ return (
+
+ );
+}
+
+export {
+ Table,
+ TableHeader,
+ TableBody,
+ TableFooter,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableCaption,
+};
diff --git a/services/web/src/components/ui/tabs.jsx b/services/web/src/components/ui/tabs.jsx
new file mode 100644
index 000000000..4fe0ec281
--- /dev/null
+++ b/services/web/src/components/ui/tabs.jsx
@@ -0,0 +1,51 @@
+import * as TabsPrimitive from '@radix-ui/react-tabs';
+
+import { cn } from '@/lib/utils';
+
+function Tabs({ className, ...props }) {
+ return (
+
+ );
+}
+
+function TabsList({ className, ...props }) {
+ return (
+
+ );
+}
+
+function TabsTrigger({ className, ...props }) {
+ return (
+
+ );
+}
+
+function TabsContent({ className, ...props }) {
+ return (
+
+ );
+}
+
+export { Tabs, TabsList, TabsTrigger, TabsContent };
diff --git a/services/web/src/components/ui/textarea.jsx b/services/web/src/components/ui/textarea.jsx
new file mode 100644
index 000000000..45770dd86
--- /dev/null
+++ b/services/web/src/components/ui/textarea.jsx
@@ -0,0 +1,16 @@
+import { cn } from '@/lib/utils';
+
+function Textarea({ className, ...props }) {
+ return (
+
+ );
+}
+
+export { Textarea };
diff --git a/services/web/src/components/ui/tooltip.jsx b/services/web/src/components/ui/tooltip.jsx
new file mode 100644
index 000000000..eb8788636
--- /dev/null
+++ b/services/web/src/components/ui/tooltip.jsx
@@ -0,0 +1,45 @@
+import * as TooltipPrimitive from '@radix-ui/react-tooltip';
+
+import { cn } from '@/lib/utils';
+
+function TooltipProvider({ delayDuration = 0, ...props }) {
+ return (
+
+ );
+}
+
+function Tooltip({ ...props }) {
+ return (
+
+
+
+ );
+}
+
+function TooltipTrigger({ ...props }) {
+ return ;
+}
+
+function TooltipContent({ className, sideOffset = 4, children, ...props }) {
+ return (
+
+
+ {children}
+
+
+
+ );
+}
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
diff --git a/services/web/src/docs/components/EditButton.js b/services/web/src/docs/components/EditButton.js
index 2a966f167..90fd3362c 100644
--- a/services/web/src/docs/components/EditButton.js
+++ b/services/web/src/docs/components/EditButton.js
@@ -1,6 +1,7 @@
-import { ActionIcon } from '@mantine/core';
import { PiPencilSimpleBold } from 'react-icons/pi';
+import { Button } from '@/components/ui/button';
+
import { useClass } from 'helpers/bem';
import { useDocs } from '../utils/context';
@@ -9,11 +10,12 @@ export default function EditButton() {
const { mode, setMode } = useDocs();
const className = useClass('edit-button', mode === 'edit' ? 'active' : null);
return (
- setMode(mode === 'view' ? 'edit' : 'view')}>
-
+
);
}
diff --git a/services/web/src/docs/components/EditFieldModal.js b/services/web/src/docs/components/EditFieldModal.js
index eaa1780a4..223e81e18 100644
--- a/services/web/src/docs/components/EditFieldModal.js
+++ b/services/web/src/docs/components/EditFieldModal.js
@@ -1,10 +1,19 @@
-import { Button, Checkbox, Stack, TextInput, Textarea } from '@mantine/core';
import { get } from 'lodash';
import { useCallback, useEffect, useState } from 'react';
+import { Button } from '@/components/ui/button';
+import { Checkbox } from '@/components/ui/checkbox';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { Spinner } from '@/components/ui/spinner';
+import { Textarea } from '@/components/ui/textarea';
+
import ErrorMessage from 'components/ErrorMessage';
+import { useModalContext } from 'components/ModalWrapper';
export default function EditFieldModal(props) {
+ const { close } = useModalContext();
+
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const [updateModel, setUpdateModel] = useState(false);
@@ -39,7 +48,7 @@ export default function EditFieldModal(props) {
}
await props.updatePath(updatePath, value);
setLoading(false);
- props.close();
+ close();
} catch (err) {
setError(err);
setLoading(false);
@@ -57,53 +66,48 @@ export default function EditFieldModal(props) {
const { model } = props;
if (model) {
return (
- setUpdateModel(event.currentTarget.checked)}
- mt="md"
- />
+
+ setUpdateModel(checked === true)}
+ />
+ {`Update base ${model.toLowerCase()}.`}
+
);
}
return null;
}
return (
- <>
-
-
+
+
+
+ {props.markdown ? (
+
+ ) : (
+
+ )}
+ {renderUpdateModel()}
+
-
- Save
-
-
- >
+
+ {loading && }
+ Save
+
+
);
}
diff --git a/services/web/src/docs/components/EditableField.js b/services/web/src/docs/components/EditableField.js
index 37b836c76..5e08dc938 100644
--- a/services/web/src/docs/components/EditableField.js
+++ b/services/web/src/docs/components/EditableField.js
@@ -1,4 +1,3 @@
-import { Text } from '@mantine/core';
import { get, startCase } from 'lodash';
import PropTypes from 'prop-types';
import { useContext } from 'react';
@@ -40,19 +39,6 @@ export default function DocsEditableField(props) {
return [mode === 'edit' ? 'editable' : null, value ? 'filled' : 'empty'];
}
- function renderModalContent({ close }) {
- return (
-
- );
- }
-
function render() {
if (mode === 'edit') {
return (
@@ -60,9 +46,19 @@ export default function DocsEditableField(props) {
title={[
`Edit ${startCase(type)}`,
props.markdown && ' - Supports Markdown',
- ].filter(Boolean)}
- component={renderModalContent}
- trigger={{renderValue()} }
+ ]
+ .filter(Boolean)
+ .join('')}
+ component={
+
+ }
+ trigger={{renderValue()} }
/>
);
} else {
diff --git a/services/web/src/docs/components/Properties.js b/services/web/src/docs/components/Properties.js
index df03324f5..01fded46d 100644
--- a/services/web/src/docs/components/Properties.js
+++ b/services/web/src/docs/components/Properties.js
@@ -1,8 +1,13 @@
-import { Popover } from '@mantine/core';
import { get, isEqual } from 'lodash';
import PropTypes from 'prop-types';
import React from 'react';
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from '@/components/ui/tooltip';
+
import { useClass } from 'helpers/bem';
import { JumpLink } from 'components/Link';
@@ -201,12 +206,14 @@ export default function DocsProperties(props) {
return (
{renderTypes(oneOf[0])}
-
-
+
+
*
-
- May also be an array.
-
+
+
+ May also be an array.
+
+
);
}
diff --git a/services/web/src/docs/components/RequestBuilder.js b/services/web/src/docs/components/RequestBuilder.js
index ad4089e83..de2d124e8 100644
--- a/services/web/src/docs/components/RequestBuilder.js
+++ b/services/web/src/docs/components/RequestBuilder.js
@@ -1,21 +1,3 @@
-import {
- ActionIcon,
- Affix,
- Divider,
- Drawer,
- Fieldset,
- Group,
- LoadingOverlay,
- Paper,
- SegmentedControl,
- Stack,
- Switch,
- Tabs,
- Text,
- TextInput,
-} from '@mantine/core';
-
-import { useDisclosure } from '@mantine/hooks';
import { get, set } from 'lodash';
import React, { useState } from 'react';
@@ -27,6 +9,26 @@ import {
PiTrashBold,
} from 'react-icons/pi';
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { Separator } from '@/components/ui/separator';
+import {
+ Sheet,
+ SheetContent,
+ SheetHeader,
+ SheetTitle,
+} from '@/components/ui/sheet';
+import { Spinner } from '@/components/ui/spinner';
+import { Switch } from '@/components/ui/switch';
+import {
+ Tabs,
+ TabsContent,
+ TabsList,
+ TabsTrigger,
+} from '@/components/ui/tabs';
+import { cn } from '@/lib/utils';
+
import Code from 'components/Code';
import ErrorMessage from 'components/ErrorMessage';
import RequestBlock from 'components/RequestBlock';
@@ -56,7 +58,15 @@ const TYPE_RANK = {
};
export default function RequestBuilder(props) {
- const [opened, { open, close }] = useDisclosure(false);
+ const [opened, setOpened] = useState(false);
+
+ function open() {
+ setOpened(true);
+ }
+
+ function close() {
+ setOpened(false);
+ }
const { route, trigger } = props;
@@ -153,13 +163,13 @@ export default function RequestBuilder(props) {
function renderRequestPane() {
return (
-
+
{renderParameters()}
{renderQuery()}
{renderBody()}
-
+
{renderOutput()}
-
+
);
}
@@ -207,9 +217,7 @@ export default function RequestBuilder(props) {
if (schema?.properties) {
return (
<>
-
- Body
-
+ Body
{renderSchema(schema, ['body'])}
>
);
@@ -275,9 +283,9 @@ export default function RequestBuilder(props) {
{key}{' '}
-
+
{open ? : }
-
+
{open && (
@@ -292,8 +300,9 @@ export default function RequestBuilder(props) {
{key}{' '}
- {
const p = [...path, key];
const values = get(req, p, []);
@@ -301,7 +310,7 @@ export default function RequestBuilder(props) {
setReq({ ...req });
}}>
-
+
{renderSchema(schema, [...path, key])}
@@ -323,12 +332,13 @@ export default function RequestBuilder(props) {
{values.map((value, i) => {
return (
-
+
{renderSchema(items, [...path, i], {
...options,
icon: (
-
{
const updated = values.filter((value, j) => {
return j !== i;
@@ -337,10 +347,10 @@ export default function RequestBuilder(props) {
setReq({ ...req });
}}>
-
+
),
})}
-
+
);
})}
@@ -372,34 +382,42 @@ export default function RequestBuilder(props) {
return renderCheckbox(path, options);
}
- function renderInput(path, options) {
+ function renderInput(path, options = {}) {
+ const { label, icon, ...rest } = options;
const value = get(req, path);
return (
-
{
- setField(event, { value: e.target.value, path, ...options });
- }}
- autoComplete="chrome-off"
- spellCheck="false"
- />
+
+ {label &&
{label} }
+
+ {
+ setField(e, { value: e.target.value, path, ...options });
+ }}
+ autoComplete="chrome-off"
+ spellCheck="false"
+ />
+ {icon}
+
+
);
}
- function renderCheckbox(path, options) {
+ function renderCheckbox(path, options = {}) {
+ const { label, icon } = options;
const value = get(req, path);
return (
- {
- setField(event, { value: e.target.checked, path, ...options });
- }}
- {...options}
- />
+
+ {
+ setField(null, { checked, type: 'checkbox', path, ...options });
+ }}
+ />
+ {label && {label} }
+ {icon}
+
);
}
@@ -419,15 +437,11 @@ export default function RequestBuilder(props) {
function renderResponsePane() {
if (res || error) {
return (
-
+
{res &&
{JSON.stringify(res, null, 2)}}
- {recorded && (
-
- Response Recorded
-
- )}
-
+ {recorded &&
Response Recorded
}
+
);
}
}
@@ -437,49 +451,60 @@ export default function RequestBuilder(props) {
{React.cloneElement(trigger, {
onClick: open,
})}
-
-
-
-
- setActiveTab(value === 'request' ? 0 : 1)}>
-
- Request
- Response
-
-
-
- {renderRequestPane()}
-
-
-
- {renderResponsePane()}
-
-
-
-
-
-
- {canEditDocs() && (
-
-
-
- )}
-
-
-
-
-
-
-
+ (value ? open() : close())}>
+
+
+ {route}
+
+
+ {loading && (
+
+
+
+ )}
+
+
+ setActiveTab(value === 'request' ? 0 : 1)
+ }>
+
+ Request
+ Response
+
+
+
+ {renderRequestPane()}
+
+
+
+ {renderResponsePane()}
+
+
+
+
+ {canEditDocs() && (
+
+
+
+ )}
+
+
+
+
+
+
);
}
@@ -509,24 +534,34 @@ function AnyOfSchema(props) {
const [selected, setSelected] = useState(0);
+ const items = anyOf
+ .map((schema, i) => {
+ return {
+ label: schema.type || '',
+ value: i,
+ };
+ })
+ .filter((item) => {
+ return item.label;
+ });
+
return (
- {
- return {
- label: schema.type || '',
- value: i,
- };
- })
- .filter((item) => {
- return item.label;
- })}
- onChange={(value) => {
- setSelected(value);
- }}
- />
+
+ {items.map((item) => {
+ return (
+ setSelected(item.value)}>
+ {item.label}
+
+ );
+ })}
+
{renderSchema(anyOf[selected])}
);
diff --git a/services/web/src/docs/components/RouteAuthentication.js b/services/web/src/docs/components/RouteAuthentication.js
index 4e5c653d5..bf4bbc896 100644
--- a/services/web/src/docs/components/RouteAuthentication.js
+++ b/services/web/src/docs/components/RouteAuthentication.js
@@ -1,5 +1,4 @@
import { Link } from '@bedrockio/router';
-import { Anchor } from '@mantine/core';
import { get } from 'lodash';
import React from 'react';
@@ -44,15 +43,19 @@ export default class Route extends React.Component {
renderAuth() {
if (this.authRequired()) {
return (
-
+
Required
-
+
);
} else if (this.authOptional()) {
return (
-
+
Optional
-
+
);
} else {
return 'None';
diff --git a/services/web/src/docs/components/RouteExample.js b/services/web/src/docs/components/RouteExample.js
index b44ffd63d..b4dc610c1 100644
--- a/services/web/src/docs/components/RouteExample.js
+++ b/services/web/src/docs/components/RouteExample.js
@@ -1,7 +1,8 @@
-import { ActionIcon, Group, Text } from '@mantine/core';
import { useState } from 'react';
import { PiMinus, PiPlus, PiTrashBold } from 'react-icons/pi';
+import { Button } from '@/components/ui/button';
+
import { useClass } from 'helpers/bem';
import { JumpLink } from 'components/Link';
@@ -70,14 +71,9 @@ export default function RouteExample(props) {
-
-
-
- {status}
-
+
+
+
{status}
+
-
+
}
/>
-
-
+
+
{canEditDocs() && (
Are you sure you want to delete this example?
+ Are you sure you want to delete this example?
}
trigger={
- {
evt.stopPropagation();
}}>
-
+
}
/>
)}
{open ? : }
-
-
+
+
{open && (
diff --git a/services/web/src/docs/components/RouteParams.js b/services/web/src/docs/components/RouteParams.js
index 4f6b40807..4fe5409ad 100644
--- a/services/web/src/docs/components/RouteParams.js
+++ b/services/web/src/docs/components/RouteParams.js
@@ -1,8 +1,9 @@
-import { Alert } from '@mantine/core';
import { get } from 'lodash';
import PropTypes from 'prop-types';
import React from 'react';
+import { Alert, AlertDescription } from '@/components/ui/alert';
+
import {
expandRoute,
getModelPath,
@@ -115,7 +116,11 @@ export default class RouteParams extends React.Component {
const queryParams = this.getQueryParams();
if (!routeEntry) {
- return
No OpenApi entry found. ;
+ return (
+
+ No OpenApi entry found.
+
+ );
} else if (requestBody) {
const { path, mime } = requestBody;
return (
diff --git a/services/web/src/docs/components/RoutePath.js b/services/web/src/docs/components/RoutePath.js
index e6a4de5e1..fc47b98d8 100644
--- a/services/web/src/docs/components/RoutePath.js
+++ b/services/web/src/docs/components/RoutePath.js
@@ -1,7 +1,8 @@
-import { ActionIcon } from '@mantine/core';
import React from 'react';
import { PiPlayBold } from 'react-icons/pi';
+import { Button } from '@/components/ui/button';
+
import Code from 'components/Code';
import RequestBuilder from './RequestBuilder';
@@ -15,9 +16,9 @@ export default class Route extends React.Component {
+
-
+
}
/>
}>
diff --git a/services/web/src/docs/components/VisitedSchemas.js b/services/web/src/docs/components/VisitedSchemas.js
index 4142e0459..5783d2649 100644
--- a/services/web/src/docs/components/VisitedSchemas.js
+++ b/services/web/src/docs/components/VisitedSchemas.js
@@ -1,6 +1,7 @@
-import { Divider } from '@mantine/core';
import React from 'react';
+import { Separator } from '@/components/ui/separator';
+
import { expandRef } from 'docs/utils';
import EditableField from './EditableField';
@@ -25,7 +26,7 @@ export default class VisitedSchemas extends React.Component {
const { name, path } = expandRef(ref);
return (
- {i > 0 && }
+ {i > 0 && }
{name}
diff --git a/services/web/src/docs/screens/ApiDocs/index.js b/services/web/src/docs/screens/ApiDocs/index.js
index aba5b170d..026469d77 100644
--- a/services/web/src/docs/screens/ApiDocs/index.js
+++ b/services/web/src/docs/screens/ApiDocs/index.js
@@ -1,8 +1,9 @@
import { useLocation, useNavigate } from '@bedrockio/router';
-import { ActionIcon, Group, Text } from '@mantine/core';
import { useEffect } from 'react';
import { PiArrowClockwiseBold } from 'react-icons/pi';
+import { Button } from '@/components/ui/button';
+
import { useClass } from 'helpers/bem';
import PortalLayout from 'layouts/Portal';
@@ -92,7 +93,7 @@ export default function ApiDocs() {
function renderActions() {
if (canEditDocs()) {
return (
-
+
+
Generates OpenApi documentation based on schemas and route
validation. This will not overwrite current documentation.
-
+
}
confirmButton="Generate Documentation"
trigger={
-
+
-
+
}
/>
-
+
);
}
}
diff --git a/services/web/src/helpers/notifications.js b/services/web/src/helpers/notifications.js
index 80c9bfbbe..bb783eaaf 100644
--- a/services/web/src/helpers/notifications.js
+++ b/services/web/src/helpers/notifications.js
@@ -1,10 +1,5 @@
-import { showNotification } from '@mantine/notifications';
-import { PiCheckBold } from 'react-icons/pi';
+import { notifySuccess } from 'utils/notify';
export function showSuccessNotification(params) {
- showNotification({
- ...params,
- color: 'green',
- icon: ,
- });
+ notifySuccess(params);
}
diff --git a/services/web/src/hooks/useDisclosure.js b/services/web/src/hooks/useDisclosure.js
new file mode 100644
index 000000000..f0c76c9ab
--- /dev/null
+++ b/services/web/src/hooks/useDisclosure.js
@@ -0,0 +1,15 @@
+import { useCallback, useState } from 'react';
+
+/**
+ * Boolean open/close state helper for menus, drawers and modals.
+ * Returns [opened, { open, close, toggle }].
+ */
+export function useDisclosure(initial = false) {
+ const [opened, setOpened] = useState(initial);
+ const open = useCallback(() => setOpened(true), []);
+ const close = useCallback(() => setOpened(false), []);
+ const toggle = useCallback(() => setOpened((o) => !o), []);
+ return [opened, { open, close, toggle }];
+}
+
+export default useDisclosure;
diff --git a/services/web/src/hooks/useMediaQuery.js b/services/web/src/hooks/useMediaQuery.js
new file mode 100644
index 000000000..022228036
--- /dev/null
+++ b/services/web/src/hooks/useMediaQuery.js
@@ -0,0 +1,24 @@
+import { useEffect, useState } from 'react';
+
+/**
+ * Local replacement for the Mantine useMediaQuery.
+ * Returns true when the media query matches.
+ */
+export function useMediaQuery(query, initial = false) {
+ const [matches, setMatches] = useState(initial);
+
+ useEffect(() => {
+ if (typeof window === 'undefined' || !window.matchMedia) {
+ return;
+ }
+ const mql = window.matchMedia(query);
+ const onChange = () => setMatches(mql.matches);
+ onChange();
+ mql.addEventListener('change', onChange);
+ return () => mql.removeEventListener('change', onChange);
+ }, [query]);
+
+ return matches;
+}
+
+export default useMediaQuery;
diff --git a/services/web/src/layouts/Basic.js b/services/web/src/layouts/Basic.js
index c06ca0af7..b1b7def2f 100644
--- a/services/web/src/layouts/Basic.js
+++ b/services/web/src/layouts/Basic.js
@@ -1,24 +1,18 @@
-import { Group, Paper, Stack } from '@mantine/core';
-
import ConnectionError from 'components/ConnectionError';
import Logo from 'components/Logo';
+import { Card } from '@/components/ui/card';
+
export default function BasicLayout({ children }) {
return (
-
+
-
-
-
-
- {children}
-
-
-
+
);
}
diff --git a/services/web/src/layouts/Dashboard.js b/services/web/src/layouts/Dashboard.js
index 590282768..8d8e2b44d 100644
--- a/services/web/src/layouts/Dashboard.js
+++ b/services/web/src/layouts/Dashboard.js
@@ -1,18 +1,5 @@
import { NavLink, useLocation } from '@bedrockio/router';
-
-import {
- AppShell,
- Box,
- Burger,
- Button,
- Center,
- Divider,
- Flex,
- ScrollArea,
- Text,
-} from '@mantine/core';
-
-import { useDisclosure, useMediaQuery } from '@mantine/hooks';
+import { Building2, ChevronDown, Menu } from 'lucide-react';
import React, { useEffect } from 'react';
import {
@@ -30,49 +17,35 @@ import {
PiUserBold,
} from 'react-icons/pi';
-import { TbChevronDown } from 'react-icons/tb';
-
import { useSession } from 'stores/session';
import ConnectionError from 'components/ConnectionError';
import ErrorBoundary from 'components/ErrorBoundary';
import Footer from 'components/Footer';
import Logo from 'components/Logo';
+import MenuItem from 'components/MenuItem';
import ModalTrigger from 'components/ModalWrapper';
import OrganizationSelector from 'components/OrganizationSelector';
+import { useDisclosure } from 'hooks/useDisclosure';
+import { useMediaQuery } from 'hooks/useMediaQuery';
+
import { userCanSwitchOrganizations } from 'utils/permissions';
-import MenuItem from '../components/MenuItem';
+import { Button } from '@/components/ui/button';
const menuItems = [
- {
- icon: PiStorefrontBold,
- url: '/shops',
- label: 'Shops',
- },
- {
- icon: PiTagBold,
- url: '/products',
- label: 'Products',
- },
+ { icon: PiStorefrontBold, url: '/shops', label: 'Shops' },
+ { icon: PiTagBold, url: '/products', label: 'Products' },
{
icon: PiUserBold,
label: 'Users',
url: '/users',
items: [
- {
- icon: PiEnvelopeSimpleBold,
- label: 'Invites',
- url: '/users/invites',
- },
+ { icon: PiEnvelopeSimpleBold, label: 'Invites', url: '/users/invites' },
],
},
- {
- icon: PiBuildingOfficeBold,
- url: '/organizations',
- label: 'Organizations',
- },
+ { icon: PiBuildingOfficeBold, url: '/organizations', label: 'Organizations' },
];
const accountItems = [
@@ -80,134 +53,109 @@ const accountItems = [
icon: PiTerminalBold,
label: 'System',
items: [
- {
- icon: PiFileBold,
- url: '/templates',
- label: 'Templates',
- },
- {
- icon: PiListMagnifyingGlass,
- url: '/audit-log',
- label: 'Audit Log',
- },
- {
- icon: PiGridFourBold,
- url: '/applications',
- label: 'Applications',
- },
- {
- icon: PiBookBold,
- url: '/docs',
- label: 'API Docs',
- },
+ { icon: PiFileBold, url: '/templates', label: 'Templates' },
+ { icon: PiListMagnifyingGlass, url: '/audit-log', label: 'Audit Log' },
+ { icon: PiGridFourBold, url: '/applications', label: 'Applications' },
+ { icon: PiBookBold, url: '/docs', label: 'API Docs' },
],
},
- {
- icon: PiGearBold,
- url: '/settings',
- label: 'My Settings',
- },
- {
- icon: PiDoorBold,
- url: '/logout',
- label: 'Log Out',
- },
+ { icon: PiGearBold, url: '/settings', label: 'My Settings' },
+ { icon: PiDoorBold, url: '/logout', label: 'Log Out' },
];
export default function DashboardLayout({ children }) {
const { user, organization } = useSession();
const [opened, { toggle, close }] = useDisclosure();
- const isMobile = useMediaQuery('(max-width: 62em)');
-
+ const isMobile = useMediaQuery('(max-width: 62em)', false);
const location = useLocation();
useEffect(() => {
close();
}, [location.pathname]);
+ const sidebar = (
+
+
+
+
+
+ {userCanSwitchOrganizations(user) && (
+
+
+
+
+ {organization?.name || 'Select Organization'}
+
+
+
+ }>
+
+
+
+
+ )}
+
+
+
+ {menuItems.map((item) => (
+
+ ))}
+
+
+
+ {accountItems.map((item) => (
+
+ ))}
+
+
+ );
+
return (
-
-
-
-
-
+
+ {!isMobile && (
+
+ )}
+
+ {isMobile && opened && (
+
+ )}
+
+
+ {isMobile && (
+
+ )}
+
+
+
+
+ {children}
-
-
-
-
-
-
-
-
-
- {userCanSwitchOrganizations(user) && (
-
- }
- leftSection={ }>
-
- {organization?.name || 'Select Organization'}
-
-
- }>
-
-
-
-
- )}
-
-
- {menuItems.map((item) => (
-
- ))}
-
-
- {accountItems.map((item) => (
-
- ))}
-
-
-
-
-
- {children}
-
-
-
-
+
+
+
+
);
}
diff --git a/services/web/src/layouts/Portal.js b/services/web/src/layouts/Portal.js
index 0f818cd68..5128906a2 100644
--- a/services/web/src/layouts/Portal.js
+++ b/services/web/src/layouts/Portal.js
@@ -1,23 +1,19 @@
import { Link, useLocation } from '@bedrockio/router';
-
-import {
- AppShell,
- Burger,
- Button,
- Flex,
- Group,
- ScrollArea,
-} from '@mantine/core';
-
-import { useDisclosure } from '@mantine/hooks';
+import { Menu } from 'lucide-react';
import { useEffect } from 'react';
import ConnectionError from 'components/ConnectionError';
import Logo from 'components/Logo';
import MenuItem from 'components/MenuItem';
+import { useDisclosure } from 'hooks/useDisclosure';
+import { useMediaQuery } from 'hooks/useMediaQuery';
+
+import { Button } from '@/components/ui/button';
+
export default function PortalLayout({ children, menuItems, actions }) {
const [opened, { toggle, close }] = useDisclosure();
+ const isMobile = useMediaQuery('(max-width: 48em)', false);
const location = useLocation();
@@ -25,56 +21,58 @@ export default function PortalLayout({ children, menuItems, actions }) {
close();
}, [location.pathname]);
+ const navbar = (
+
+
+ {menuItems.map((item) => (
+
+ ))}
+
+
{actions}
+
+ );
+
return (
- <>
-
-
-
-
-
-
-
-
- Go to Dashboard
-
-
-
-
-
- {menuItems.map((item) => (
-
- ))}
-
- {actions}
-
-
+
+
+
+
+ {!isMobile && (
+
+ )}
+
+ {isMobile && opened && (
+
+ )}
+
+
{children}
-
-
- >
+
+
+
);
}
diff --git a/services/web/src/lib/utils.js b/services/web/src/lib/utils.js
new file mode 100644
index 000000000..2f8c0f695
--- /dev/null
+++ b/services/web/src/lib/utils.js
@@ -0,0 +1,10 @@
+import { clsx } from 'clsx';
+import { twMerge } from 'tailwind-merge';
+
+/**
+ * Merge class names with Tailwind-aware conflict resolution.
+ * Used by every shadcn/ui component.
+ */
+export function cn(...inputs) {
+ return twMerge(clsx(inputs));
+}
diff --git a/services/web/src/modals/Confirm.js b/services/web/src/modals/Confirm.js
index 15804a062..026c1de9d 100644
--- a/services/web/src/modals/Confirm.js
+++ b/services/web/src/modals/Confirm.js
@@ -1,18 +1,20 @@
-import { Button } from '@mantine/core';
import React, { useState } from 'react';
+import { Button } from '@/components/ui/button';
+import { Spinner } from '@/components/ui/spinner';
+
import ErrorMessage from 'components/ErrorMessage';
import ModalWrapper, { useModalContext } from 'components/ModalWrapper';
import Actions from 'components/form-fields/Actions';
/**
- * Confirm dialog component using Mantine Modal.
+ * Confirm dialog component using shadcn Dialog.
*
* @param {object} props
* @param {React.ReactNode} props.title - Title.
* @param {React.ReactNode} props.content - Content.
* @param {string} [props.confirmButton] - Confirm button label.
- * @param {boolean} [props.negative] - If true, confirm button is red.
+ * @param {boolean} [props.negative] - If true, confirm button is destructive.
* @param {function} [props.onConfirm] - Async function called on confirm.
* @returns {JSX.Element}
*/
@@ -47,10 +49,14 @@ function Confirm(props) {
{content}
-
+
Cancel
-
+
+ {loading && }
{confirmButton}
diff --git a/services/web/src/modals/InspectObject.js b/services/web/src/modals/InspectObject.js
index ad99a2df4..9e462bbf5 100644
--- a/services/web/src/modals/InspectObject.js
+++ b/services/web/src/modals/InspectObject.js
@@ -1,17 +1,10 @@
-import {
- ActionIcon,
- Checkbox,
- CopyButton,
- Group,
- Stack,
- Tooltip,
-} from '@mantine/core';
-
import JsonView from '@uiw/react-json-view';
import { darkTheme } from '@uiw/react-json-view/dark';
import { useState } from 'react';
import { PiCheck, PiCopy } from 'react-icons/pi';
+import { Button } from '@/components/ui/button';
+
import ModalWrapper from 'components/ModalWrapper';
/**
@@ -23,45 +16,53 @@ import ModalWrapper from 'components/ModalWrapper';
*/
function InspectObject({ object }) {
const [expandAll, setExpandAll] = useState(false);
+ const [copied, setCopied] = useState(false);
+
+ function onCopy() {
+ navigator.clipboard.writeText(JSON.stringify(object, null, 2));
+ setCopied(true);
+ setTimeout(() => {
+ setCopied(false);
+ }, 2000);
+ }
return (
-
-
- {
- setExpandAll(!expandAll);
+
+
+
+ {
+ setExpandAll(!expandAll);
+ }}
+ />
+ Expand all
+
+
+
+ {copied ? : }
+
+
+
+
-
-
- {({ copied, copy }) => (
-
-
- {copied ? : }
-
-
- )}
-
-
-
-
+
+
);
}
diff --git a/services/web/src/screens/Applications/Actions.js b/services/web/src/screens/Applications/Actions.js
index b2f08c3db..e4c25362d 100644
--- a/services/web/src/screens/Applications/Actions.js
+++ b/services/web/src/screens/Applications/Actions.js
@@ -1,5 +1,4 @@
import { Link } from '@bedrockio/router';
-import { ActionIcon, Menu, Text } from '@mantine/core';
import {
PiDotsThreeOutlineVerticalBold,
@@ -9,24 +8,32 @@ import {
import Confirm from 'modals/Confirm';
+import { Button } from '@/components/ui/button';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+
import { request } from 'utils/api';
export default function ApplicationActions({ application, reload }) {
return (
-
-
-
+
+
+
-
-
+
+
-
- }>
- Edit
-
+
+
+
+
+ Edit
+
+
+
Are you sure you want to delete{' '}
{application.name} ?
-
+
}
trigger={
- }>
+ e.preventDefault()}>
+
Delete
-
+
}
/>
-
-
+
+
);
}
diff --git a/services/web/src/screens/Applications/Details/Edit.js b/services/web/src/screens/Applications/Details/Edit.js
index 48409d97e..41e84fcf6 100644
--- a/services/web/src/screens/Applications/Details/Edit.js
+++ b/services/web/src/screens/Applications/Details/Edit.js
@@ -1,10 +1,11 @@
import { Link, useNavigate } from '@bedrockio/router';
-import { Button, Stack } from '@mantine/core';
import { usePage } from 'stores/page';
import PageHeader from 'components/PageHeader';
+import { Button } from '@/components/ui/button';
+
import Form from '../Form';
export default function EditApplication() {
@@ -12,7 +13,7 @@ export default function EditApplication() {
const navigate = useNavigate();
return (
-
+
- Show
+
+ Show
}
/>
@@ -33,6 +34,6 @@ export default function EditApplication() {
navigate(`/applications`);
}}
/>
-
+
);
}
diff --git a/services/web/src/screens/Applications/Form.js b/services/web/src/screens/Applications/Form.js
index 226fbac7c..697fbf89e 100644
--- a/services/web/src/screens/Applications/Form.js
+++ b/services/web/src/screens/Applications/Form.js
@@ -1,30 +1,40 @@
-import {
- Button,
- Fieldset,
- Grid,
- Group,
- Stack,
- TextInput,
- Textarea,
-} from '@mantine/core';
-
-import { useForm } from '@mantine/form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
import ErrorMessage from 'components/ErrorMessage';
+import { Button } from '@/components/ui/button';
+import { Card, CardContent } from '@/components/ui/card';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { Spinner } from '@/components/ui/spinner';
+import { Textarea } from '@/components/ui/textarea';
+
import { useRequest } from 'utils/api';
+const schema = z.object({
+ name: z.string().min(1, 'Name is required'),
+ description: z.string().optional(),
+});
+
export default function ApplicationForm({ application, onSave }) {
const isUpdate = !!application;
const form = useForm({
- initialValues: {
+ resolver: zodResolver(schema),
+ defaultValues: {
name: application?.name || '',
description: application?.description || '',
},
- validate: {
- name: (value) => (!value ? 'Name is required' : null),
- },
});
const { loading, error, request } = useRequest({
@@ -32,47 +42,77 @@ export default function ApplicationForm({ application, onSave }) {
path: isUpdate ? `/1/applications/${application.id}` : '/1/applications',
});
- const onSubmit = async (values) => {
+ async function onSubmit(values) {
await request({
body: {
...values,
},
});
onSave();
- };
+ }
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/* Add any additional fields or components here */}
-
-
-
-
-
- {isUpdate ? 'Update Application' : 'Create New Application'}
-
-
-
+
+
+
+
+
+
+
+ Application Details
+
+ (
+
+ Name
+
+
+
+
+
+ )}
+ />
+ (
+
+ Description
+
+
+
+
+
+ )}
+ />
+
+
+
+
+
+ {isUpdate && application.apiKey && (
+
+ API Key
+
+ {application.apiKey}
+
+
+ )}
+
+
+
+
+
+ {loading && }
+ {isUpdate ? 'Update Application' : 'Create New Application'}
+
+
+
+
);
}
diff --git a/services/web/src/screens/Applications/List.js b/services/web/src/screens/Applications/List.js
index bff0d2646..fb8adc5da 100644
--- a/services/web/src/screens/Applications/List.js
+++ b/services/web/src/screens/Applications/List.js
@@ -1,10 +1,18 @@
import { Link } from '@bedrockio/router';
-import { Button, Code, Stack, Table } from '@mantine/core';
import Meta from 'components/Meta';
import PageHeader from 'components/PageHeader';
import Search from 'components/Search';
+import { Button } from '@/components/ui/button';
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHeader,
+ TableRow,
+} from '@/components/ui/table';
+
import { request } from 'utils/api';
import { fromNow } from 'utils/date';
@@ -25,74 +33,63 @@ export default function Applications() {
{({ items, reload }) => {
return (
-
+
}
rightSection={
-
- New Application
+
+ New Application
}
/>
-
-
-
+
+
+
Name
-
- Description
-
+ Description
APIKey
Last Used
-
+
Actions
-
-
-
+
+
+
{items.map((item) => {
return (
-
- {item.name}
- {item.description}
-
- {item.apiKey}
-
-
+
+ {item.name}
+ {item.description}
+
+
+ {item.apiKey}
+
+
+
{item.lastUsedAt ? fromNow(item.lastUsedAt) : 'N / A'}
-
-
-
-
-
+
+
+
+
+
);
})}
-
+
+
-
+
);
}}
diff --git a/services/web/src/screens/Applications/New.js b/services/web/src/screens/Applications/New.js
index 19a4b16e8..61e160b04 100644
--- a/services/web/src/screens/Applications/New.js
+++ b/services/web/src/screens/Applications/New.js
@@ -1,15 +1,16 @@
import { Link, useNavigate } from '@bedrockio/router';
-import { Button, Stack } from '@mantine/core';
import PageHeader from 'components/PageHeader';
+import { Button } from '@/components/ui/button';
+
import Form from './Form';
export default function NewShop() {
const navigate = useNavigate();
return (
-
+
- Back
+
+ Back
}
/>
@@ -28,6 +29,6 @@ export default function NewShop() {
navigate(`/applications`);
}}
/>
-
+
);
}
diff --git a/services/web/src/screens/AuditLog/Details/Overview.js b/services/web/src/screens/AuditLog/Details/Overview.js
index 4a44db45e..c0127fae0 100644
--- a/services/web/src/screens/AuditLog/Details/Overview.js
+++ b/services/web/src/screens/AuditLog/Details/Overview.js
@@ -1,8 +1,14 @@
import { Link } from '@bedrockio/router';
-import { Anchor, Divider, Paper, Stack, Table, Text } from '@mantine/core';
import Code from 'components/Code';
+import { Card, CardContent } from '@/components/ui/card';
+import {
+ DefinitionItem,
+ DefinitionList,
+} from '@/components/ui/definition-list';
+import { Separator } from '@/components/ui/separator';
+
import { formatDateTime } from 'utils/date';
export default function Overview({ auditEntry }) {
@@ -11,118 +17,99 @@ export default function Overview({ auditEntry }) {
}
return (
-
+
-
- Details
-
-
-
-
- Activity
- {auditEntry.activity}
-
-
- Actor
-
-
- {auditEntry.actor.firstName} {auditEntry.actor.lastName}
-
-
-
- {auditEntry.objectType && (
-
- Object Type
- {auditEntry.objectType}
-
- )}
- {auditEntry.objectId && (
-
- Object Id
- {auditEntry.objectId}
-
- )}
- {auditEntry?.owner?.name && (
-
- Object Owner
-
-
- {auditEntry.owner.name}
- {' '}
- - {auditEntry.ownerType}
-
-
- )}
-
- Method
- {auditEntry.requestMethod}
-
-
- Path
- {auditEntry.requestUrl}
-
- {auditEntry.sessionId && (
-
- Session Id
- {auditEntry.sessionId}
-
- )}
-
- Created At
- {formatDateTime(auditEntry.createdAt)}
-
-
-
+
Details
+
+
+ {auditEntry.activity}
+
+
+
+ {auditEntry.actor.firstName} {auditEntry.actor.lastName}
+
+
+ {auditEntry.objectType && (
+
+ {auditEntry.objectType}
+
+ )}
+ {auditEntry.objectId && (
+
+ {auditEntry.objectId}
+
+ )}
+ {auditEntry?.owner?.name && (
+
+
+ {auditEntry.owner.name}
+ {' '}
+ - {auditEntry.ownerType}
+
+ )}
+
+ {auditEntry.requestMethod}
+
+
+ {auditEntry.requestUrl}
+
+ {auditEntry.sessionId && (
+
+ {auditEntry.sessionId}
+
+ )}
+
+ {formatDateTime(auditEntry.createdAt)}
+
+
{auditEntry.attributes && (
<>
-
-
-
- Attributes
-
-
- {JSON.stringify(auditEntry.attributes || {}, null, 2)}
-
-
+
+
+
+ Attributes
+
+ {JSON.stringify(auditEntry.attributes || {}, null, 2)}
+
+
+
>
)}
{auditEntry.objectBefore && (
<>
-
-
-
- Before
-
-
- {JSON.stringify(auditEntry.objectBefore || {}, null, 2)}
-
-
+
+
+
+ Before
+
+ {JSON.stringify(auditEntry.objectBefore || {}, null, 2)}
+
+
+
>
)}
{auditEntry.objectAfter && (
<>
-
-
-
- After
-
-
- {JSON.stringify(auditEntry.objectAfter || {}, null, 2)}
-
-
+
+
+
+ After
+
+ {JSON.stringify(auditEntry.objectAfter || {}, null, 2)}
+
+
+
>
)}
-
+
);
}
diff --git a/services/web/src/screens/AuditLog/List.js b/services/web/src/screens/AuditLog/List.js
index 39c65bb74..4773bc574 100644
--- a/services/web/src/screens/AuditLog/List.js
+++ b/services/web/src/screens/AuditLog/List.js
@@ -1,23 +1,27 @@
import { Link } from '@bedrockio/router';
-
-import {
- ActionIcon,
- Anchor,
- Drawer,
- Group,
- Stack,
- Table,
- Text,
-} from '@mantine/core';
-
+import { Search as SearchIcon } from 'lucide-react';
import { useState } from 'react';
-import { PiMagnifyingGlass } from 'react-icons/pi';
import Meta from 'components/Meta';
import PageHeader from 'components/PageHeader';
import Search from 'components/Search';
import SearchFilters from 'components/Search/Filters';
+import { Button } from '@/components/ui/button';
+import {
+ Sheet,
+ SheetContent,
+ SheetHeader,
+ SheetTitle,
+} from '@/components/ui/sheet';
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHeader,
+ TableRow,
+} from '@/components/ui/table';
+
import { request } from 'utils/api';
import { formatDateTime } from 'utils/date';
@@ -96,71 +100,67 @@ export default function AuditLogList() {
return (
<>
- setSelectedItem(null)}
- title={`Audit Entry: ${selectedItem?.activity}`}>
-
-
+ {
+ if (!open) setSelectedItem(null);
+ }}>
+
+
+ Audit Entry: {selectedItem?.activity}
+
+
+ {selectedItem && }
+
+
+
+
{({ items }) => (
-
+
-
-
-
-
-
-
- fetchSearchOptions({ field: 'activity' })
- }
- name="activity"
- label="Activity"
- />
-
- fetchSearchOptions({ field: 'objectType' })
- }
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ fetchSearchOptions({ field: 'activity' })}
+ name="activity"
+ label="Activity"
+ />
+
+ fetchSearchOptions({ field: 'objectType' })
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+
Actor
Activity
Object Owner
@@ -168,71 +168,72 @@ export default function AuditLogList() {
Date
-
- Actions
-
-
-
-
+ Actions
+
+
+
-
-
-
+
+
+
No entries found.
-
-
-
+
+
+
{items.map((item) => {
const name = item.object?.name || item.actor?.name || '';
return (
- setSelectedItem(item)}>
-
+
{item.actor && (
-
+ to={`/users/${item.actor.id}`}
+ onClick={(e) => e.stopPropagation()}>
{item.actor.firstName} {item.actor.lastName}
-
+
)}
-
- {item.activity}
-
-
+
+ {item.activity}
+
{item.owner && (
-
+ to={`/users/${item.owner.id}`}
+ onClick={(e) => e.stopPropagation()}>
{item.owner.name}
-
+
)}
-
- {name}
- {formatDateTime(item.createdAt)}
-
-
- {
- evt.stopPropagation();
- setSelectedItem(item);
- }}>
-
-
-
-
+
+ {name}
+ {formatDateTime(item.createdAt)}
+
+
+ {
+ evt.stopPropagation();
+ setSelectedItem(item);
+ }}>
+
+
+
+
+
);
})}
-
+
+
-
+
)}
>
diff --git a/services/web/src/screens/Auth/AcceptInvite.js b/services/web/src/screens/Auth/AcceptInvite.js
index af458a275..ea12b3593 100644
--- a/services/web/src/screens/Auth/AcceptInvite.js
+++ b/services/web/src/screens/Auth/AcceptInvite.js
@@ -1,18 +1,10 @@
import { Link, useNavigate } from '@bedrockio/router';
-
-import {
- Anchor,
- Button,
- Group,
- PasswordInput,
- Stack,
- Text,
- TextInput,
-} from '@mantine/core';
-
-import { useForm } from '@mantine/form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { Loader2 } from 'lucide-react';
import { omit } from 'lodash';
import React, { useEffect, useState } from 'react';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
import { useSession } from 'stores/session';
@@ -20,39 +12,57 @@ import ErrorMessage from 'components/ErrorMessage';
import Meta from 'components/Meta';
import { useRequest } from 'hooks/request';
+import { Button } from '@/components/ui/button';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import { Input } from '@/components/ui/input';
+import { PasswordInput } from '@/components/ui/password-input';
+
import { request } from 'utils/api';
import { getUrlToken } from 'utils/token';
+const schema = z
+ .object({
+ firstName: z.string().min(1, 'First name is required'),
+ lastName: z.string().min(1, 'Last name is required'),
+ email: z.string(),
+ password: z
+ .string()
+ .min(1, 'Password is required')
+ .min(8, 'Password must be at least 8 characters'),
+ confirmPassword: z.string(),
+ })
+ .refine((values) => values.confirmPassword === values.password, {
+ message: 'Passwords do not match',
+ path: ['confirmPassword'],
+ });
+
function AcceptInvite() {
const { token, payload } = getUrlToken();
- const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const { authenticate } = useSession();
const navigate = useNavigate();
const form = useForm({
- initialValues: {
+ resolver: zodResolver(schema),
+ defaultValues: {
firstName: '',
lastName: '',
password: '',
confirmPassword: '',
email: payload?.sub || '',
},
- validate: {
- firstName: (value) => (!value ? 'First name is required' : null),
- lastName: (value) => (!value ? 'Last name is required' : null),
- password: (value) =>
- !value
- ? 'Password is required'
- : value.length < 8
- ? 'Password must be at least 8 characters'
- : null,
- confirmPassword: (value, values) =>
- value !== values.password ? 'Passwords do not match' : null,
- },
});
+ const loading = form.formState.isSubmitting;
+
const { run: checkInvite, error: checkError } = useRequest({
method: 'POST',
path: '/1/invites/check',
@@ -66,7 +76,6 @@ function AcceptInvite() {
async function handleSubmit(values) {
try {
setError(null);
- setLoading(true);
const { data } = await request({
method: 'POST',
@@ -79,7 +88,6 @@ function AcceptInvite() {
navigate(next);
} catch (err) {
setError(err);
- setLoading(false);
}
}
@@ -101,61 +109,108 @@ function AcceptInvite() {
function renderLoggedOut() {
return (
-
-
-
-
-
-
+
+
+
+
+
+ (
+
+ First Name
+
+
+
+
+
+ )}
+ />
+ (
+
+ Last Name
+
+
+
+
+
+ )}
+ />
+
+
+ (
+
+ Email
+
+
+
+
+
+ )}
+ />
+
+ (
+
+ Password
+
+
+
+
+
+ )}
/>
- (
+
+ Confirm Password
+
+
+
+
+
+ )}
/>
-
-
-
-
-
-
-
-
-
- Create Account
-
-
-
-
+
+
+ {loading && }
+ Create Account
+
+
+
+
+
Already have an account?{' '}
-
+
Login
-
-
-
+
+
+
);
}
diff --git a/services/web/src/screens/Auth/AcceptInviteAuthenticated.js b/services/web/src/screens/Auth/AcceptInviteAuthenticated.js
index 75c31f48b..4484bc625 100644
--- a/services/web/src/screens/Auth/AcceptInviteAuthenticated.js
+++ b/services/web/src/screens/Auth/AcceptInviteAuthenticated.js
@@ -1,7 +1,7 @@
-import { Button } from '@mantine/core';
-
import { useSession } from 'stores/session';
+import { Button } from '@/components/ui/button';
+
export default function AcceptInviteAuthenticated() {
const { logout } = useSession();
diff --git a/services/web/src/screens/Auth/ConfirmCode.js b/services/web/src/screens/Auth/ConfirmCode.js
index 5abf52fa6..1d693faa6 100644
--- a/services/web/src/screens/Auth/ConfirmCode.js
+++ b/services/web/src/screens/Auth/ConfirmCode.js
@@ -1,5 +1,4 @@
import { Link, Redirect, useLocation, useNavigate } from '@bedrockio/router';
-import { Alert, Anchor, Box, PinInput, Stack } from '@mantine/core';
import React, { useEffect, useMemo, useState } from 'react';
import { useSession } from 'stores/session';
@@ -7,6 +6,13 @@ import { useSession } from 'stores/session';
import ErrorMessage from 'components/ErrorMessage';
import Meta from 'components/Meta';
+import { Alert, AlertDescription } from '@/components/ui/alert';
+import {
+ InputOTP,
+ InputOTPGroup,
+ InputOTPSlot,
+} from '@/components/ui/input-otp';
+
import { request } from 'utils/api';
import { formatPhone } from 'utils/phone';
@@ -24,7 +30,7 @@ export default function ConfirmCode() {
};
}, []);
- const [code, setCode] = useState(state.code);
+ const [code, setCode] = useState(state.code || '');
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
@@ -32,21 +38,22 @@ export default function ConfirmCode() {
login();
}, []);
- async function login() {
+ async function login(value = code) {
const { email, phone, channel } = state;
- if (!canLogin()) {
+ if (!(value && (email || phone))) {
return;
}
try {
+ setLoading(true);
const method = channel === 'authenticator' ? 'totp' : 'otp';
const { data } = await request({
method: 'POST',
path: `/1/auth/${method}/login`,
body: {
- code,
+ code: value,
email,
phone,
},
@@ -60,72 +67,22 @@ export default function ConfirmCode() {
}
}
- function canLogin() {
- const { email, phone } = state;
- return code && (email || phone);
- }
-
function renderMessage() {
- if (state.type === 'link') {
- return renderLink();
+ const { channel, type } = state;
+ if (type === 'link') {
+ if (channel === 'email') {
+ return `Please click on the link sent to ${state.email}.`;
+ } else if (channel === 'sms') {
+ return `Please click on the link sent to ${formatPhone(state.phone)}.`;
+ }
} else {
- return renderCode();
- }
- }
-
- function renderLink() {
- return (
-
- {renderLinkMessage()}
-
-
- );
- }
-
- function renderLinkMessage() {
- const { channel } = state;
- if (channel === 'email') {
- return `Please click on the link sent to ${state.email}.`;
- } else if (channel === 'sms') {
- return `Please click on the link sent to ${formatPhone(state.phone)}.`;
- }
- }
-
- function renderCode() {
- return (
-
- {renderCodeMessage()}
-
- {!state.code && (
- {
- setCode(value);
- }}
- onComplete={() => {
- setLoading(true);
- login();
- }}
- disabled={loading}
- />
- )}
-
-
-
- );
- }
-
- function renderCodeMessage() {
- const { channel } = state;
- if (channel === 'email') {
- return `Please enter the code sent to ${state.email}.`;
- } else if (channel === 'sms') {
- return `Please enter the code sent to ${formatPhone(state.phone)}.`;
- } else if (channel === 'authenticator') {
- return 'Please enter the code from your authenticator app.';
+ if (channel === 'email') {
+ return `Please enter the code sent to ${state.email}.`;
+ } else if (channel === 'sms') {
+ return `Please enter the code sent to ${formatPhone(state.phone)}.`;
+ } else if (channel === 'authenticator') {
+ return 'Please enter the code from your authenticator app.';
+ }
}
}
@@ -133,15 +90,40 @@ export default function ConfirmCode() {
return ;
}
+ const showInput = state.type !== 'link' && !state.code;
+
return (
- {renderMessage()}
-
-
+ Confirm Code
+
+
+ {renderMessage()}
+
+ {showInput && (
+
login(value)}>
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
Back
-
-
+
+
);
}
diff --git a/services/web/src/screens/Auth/ForgotPassword.js b/services/web/src/screens/Auth/ForgotPassword.js
index 212a608b8..0ca5ca208 100644
--- a/services/web/src/screens/Auth/ForgotPassword.js
+++ b/services/web/src/screens/Auth/ForgotPassword.js
@@ -1,102 +1,118 @@
import { Link } from '@bedrockio/router';
-
-import {
- Alert,
- Anchor,
- Button,
- Group,
- Stack,
- TextInput,
- Title,
-} from '@mantine/core';
-
-import { useForm } from '@mantine/form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { Loader2 } from 'lucide-react';
import React, { useState } from 'react';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
import Meta from 'components/Meta';
+import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
+import { Button } from '@/components/ui/button';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import { Input } from '@/components/ui/input';
+
import { request } from 'utils/api';
+const schema = z.object({
+ email: z.string().min(1, 'Email is required').email('Invalid email'),
+});
+
export default function ForgotPassword() {
const [success, setSuccess] = useState(false);
- const [loading, setLoading] = useState(false);
+ const [email, setEmail] = useState('');
const [error, setError] = useState(null);
const form = useForm({
- initialValues: {
- email: '',
- },
- validate: {
- email: (value) => (/^\S+@\S+$/.test(value) ? null : 'Invalid email'),
- },
+ resolver: zodResolver(schema),
+ defaultValues: { email: '' },
});
+ const loading = form.formState.isSubmitting;
- const handleSubmit = async (values) => {
+ async function onSubmit(values) {
setError(null);
- setLoading(true);
-
try {
await request({
method: 'POST',
path: '/1/auth/password/request',
body: values,
});
-
+ setEmail(values.email);
setSuccess(true);
- setLoading(false);
} catch (err) {
setError(err);
- setLoading(false);
}
- };
-
- const renderMessage = () => {
- return (
-
- Please follow the instructions in the email we sent to{' '}
- {form.values.email}
-
- );
- };
-
- const renderForm = () => {
- return (
-
- {error && (
-
- {error.message || 'Something went wrong'}
-
- )}
-
-
-
-
- Reset password
-
-
- );
- };
+ }
return (
- Forgot Password
- {success ? renderMessage() : renderForm()}
+ Forgot Password
+
+ {success ? (
+
+ Mail sent!
+
+ Please follow the instructions in the email we sent to{' '}
+ {email}
+
+
+ ) : (
+
+
+ {error && (
+
+ Error
+
+ {error.message || 'Something went wrong'}
+
+
+ )}
+ (
+
+ Email
+
+
+
+
+
+ )}
+ />
+
+ {loading && }
+ Reset Password
+
+
+
+ )}
-
-
+
+
Back to login
-
-
- Don't have an account
-
-
+
+
+ Don't have an account
+
+
);
}
diff --git a/services/web/src/screens/Auth/Login.js b/services/web/src/screens/Auth/Login.js
index ae7a94e49..43606fa6c 100644
--- a/services/web/src/screens/Auth/Login.js
+++ b/services/web/src/screens/Auth/Login.js
@@ -1,17 +1,9 @@
import { Link, useNavigate } from '@bedrockio/router';
-
-import {
- Anchor,
- Button,
- PasswordInput,
- Stack,
- Text,
- TextInput,
- Title,
-} from '@mantine/core';
-
-import { useForm } from '@mantine/form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { Loader2 } from 'lucide-react';
import React, { useState } from 'react';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
import { useSession } from 'stores/session';
@@ -19,6 +11,18 @@ import Federated from 'components/Auth/Federated';
import ErrorMessage from 'components/ErrorMessage';
import Meta from 'components/Meta';
+import { Button } from '@/components/ui/button';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import { Input } from '@/components/ui/input';
+import { PasswordInput } from '@/components/ui/password-input';
+
import { request } from 'utils/api';
import { AUTH_CHANNEL, AUTH_TYPE } from 'utils/env';
@@ -50,37 +54,40 @@ async function loginOtp(body) {
});
}
+const schema = z.object({
+ email: z.string().min(1, 'Email is required').email('Enter a valid email'),
+ password:
+ AUTH_TYPE === 'password'
+ ? z.string().min(1, 'Password is required')
+ : z.string().optional(),
+});
+
export default function PasswordLogin() {
const navigate = useNavigate();
const { authenticate } = useSession();
const form = useForm({
- initialValues: {
- password: '',
+ resolver: zodResolver(schema),
+ defaultValues: {
email: '',
+ password: '',
},
});
- const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
+ const loading = form.formState.isSubmitting;
- function onAuthStart() {
- setLoading(true);
- }
+ function onAuthStart() {}
- function onAuthStop() {
- setLoading(false);
- }
+ function onAuthStop() {}
function onAuthError(error) {
setError(error);
- setLoading(false);
}
async function onSubmit(values) {
try {
setError(null);
- setLoading(true);
const { data } = await login(values);
const { token, challenge } = data;
@@ -93,57 +100,77 @@ export default function PasswordLogin() {
}
} catch (error) {
setError(error);
- setLoading(false);
}
}
return (
-
- Login
-
+ Login
-
-
-
+
+ (
+
+ Email
+
+
+
+
+
+ )}
/>
{AUTH_TYPE === 'password' && (
-
-
-
-
- Forgot password
-
-
-
+ (
+
+ Password
+
+
+
+
+
+ Forgot password
+
+
+
+
+ )}
+ />
)}
-
+
+ {loading && }
Login
-
- Don't have an account?{' '}
-
+
+ Don't have an account?{' '}
+
Register
-
-
+
+
-
-
+
+
);
}
diff --git a/services/web/src/screens/Auth/ResetPassword.js b/services/web/src/screens/Auth/ResetPassword.js
index 8c6bf1e89..4ea7d767c 100644
--- a/services/web/src/screens/Auth/ResetPassword.js
+++ b/services/web/src/screens/Auth/ResetPassword.js
@@ -1,155 +1,150 @@
import { Link, useNavigate } from '@bedrockio/router';
-
-import {
- Anchor,
- Button,
- PasswordInput,
- Stack,
- Text,
- Title,
-} from '@mantine/core';
-
-import { useForm } from '@mantine/form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { Loader2 } from 'lucide-react';
import React, { useState } from 'react';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
import { useSession } from 'stores/session';
import ErrorMessage from 'components/ErrorMessage';
import Meta from 'components/Meta';
+import { Button } from '@/components/ui/button';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import { PasswordInput } from '@/components/ui/password-input';
+
import { request } from 'utils/api';
import { getUrlToken } from 'utils/token';
+const schema = z
+ .object({
+ password: z.string().min(1, 'Password is required'),
+ repeat: z.string().min(1, 'Please repeat your password'),
+ })
+ .refine((d) => d.password === d.repeat, {
+ message: 'Passwords do not match.',
+ path: ['repeat'],
+ });
+
export default function ResetPassword() {
const navigate = useNavigate();
const { authenticate } = useSession();
const { token, payload } = getUrlToken();
- const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const [error, setError] = useState(null);
const form = useForm({
- initialValues: {
- password: '',
- repeat: '',
- },
- validate: {
- repeat: (value, values) =>
- value !== values.password ? 'Passwords do not match.' : null,
- },
+ resolver: zodResolver(schema),
+ defaultValues: { password: '', repeat: '' },
});
+ const loading = form.formState.isSubmitting;
async function onSubmit(values) {
try {
- setLoading(true);
setError(null);
-
const { data } = await request({
method: 'POST',
path: '/1/auth/password/update',
token,
- body: {
- password: values.password,
- },
+ body: { password: values.password },
});
-
setSuccess(true);
- setLoading(false);
navigate(await authenticate(data.token));
} catch (err) {
setError(err);
- setLoading(false);
}
}
- function render() {
+ if (!payload) {
return (
- {renderSwitch()}
-
- );
- }
-
- function renderSwitch() {
- if (!payload) {
- return renderTokenMissing();
- } else if (success) {
- return renderSuccessMessage();
- } else {
- return renderForm();
- }
- }
-
- function renderTokenMissing() {
- return (
-
-
+
No valid token found
-
-
+
+
Please ensure you either click the email link in the email or copy
paste the link in full.
-
+
);
}
- function renderSuccessMessage() {
+ if (success) {
return (
-
+
+
Your password has been changed!
-
-
+
+
Click here to open the{' '}
-
+
Dashboard
-
-
+
+
);
}
- function renderForm() {
- return (
-
-
- Reset Password
-
-
-
-
-
-
-
-
-
- Reset Password
-
-
+ return (
+
+
+ Reset Password
+
+
+
+ (
+
+ New Password
+
+
+
+
+
+ )}
+ />
+ (
+
+ Repeat Password
+
+
+
+
+
+ )}
+ />
+
+ {loading && }
+ Reset Password
+
-
- );
- }
-
- return render();
+
+
+ );
}
diff --git a/services/web/src/screens/Auth/Signup.js b/services/web/src/screens/Auth/Signup.js
index d04eacb8e..2891fde1e 100644
--- a/services/web/src/screens/Auth/Signup.js
+++ b/services/web/src/screens/Auth/Signup.js
@@ -1,33 +1,57 @@
import { Link, useNavigate } from '@bedrockio/router';
-
-import {
- Anchor,
- Button,
- PasswordInput,
- Stack,
- Text,
- TextInput,
- Title,
-} from '@mantine/core';
-
-import { isEmail, useForm } from '@mantine/form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { Loader2 } from 'lucide-react';
import React, { useState } from 'react';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
import { useSession } from 'stores/session';
import Federated from 'components/Auth/Federated';
import ErrorMessage from 'components/ErrorMessage';
import Meta from 'components/Meta';
-import PhoneField from 'components/form-fields/Phone';
+
+import { Button } from '@/components/ui/button';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import { Input } from '@/components/ui/input';
+import { PasswordInput } from '@/components/ui/password-input';
import { useRequest } from 'utils/api';
import { AUTH_CHANNEL, AUTH_TYPE } from 'utils/env';
+import { COUNTRIES, formatPhone } from 'utils/phone';
+
+// Normalise a typed phone number to a prefixed value (ported from
+// components/form-fields/Phone.js — that shared field migrates in Phase 4).
+function normalizePhone(value, country = 'us') {
+ let v = value
+ .trim()
+ .replace(/[ ()@.+-]/g, '')
+ .replace(/^[01](\d)/, '$1')
+ .replace(/[a-z]/gi, '');
+ return v ? `${COUNTRIES[country].prefix}${v}` : '';
+}
+
+const schema = z.object({
+ firstName: z.string().optional(),
+ lastName: z.string().optional(),
+ email: z.string().min(1, 'Email is required').email('Invalid email'),
+ phone: z.string().optional(),
+ password:
+ AUTH_TYPE === 'password'
+ ? z.string().min(1, 'Password is required')
+ : z.string().optional(),
+});
export default function SignupPassword() {
const navigate = useNavigate();
const { authenticate } = useSession();
-
- const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const signupRequest = useRequest({
@@ -45,123 +69,164 @@ export default function SignupPassword() {
},
onError: (err) => {
setError(err);
- setLoading(false);
},
});
const form = useForm({
- initialValues: {
+ resolver: zodResolver(schema),
+ defaultValues: {
firstName: '',
lastName: '',
phone: '',
password: '',
email: '',
},
- validate: {
- email: isEmail('Invalid email'),
- },
});
-
- function onAuthStart() {
- setLoading(true);
- }
-
- function onAuthStop() {
- setLoading(false);
- }
+ const loading = form.formState.isSubmitting || signupRequest.loading;
function onAuthError(error) {
setError(error);
- setLoading(false);
+ }
+
+ async function onSubmit(values) {
+ setError(null);
+ await signupRequest.request({
+ body: {
+ ...values,
+ type: AUTH_TYPE,
+ channel: AUTH_CHANNEL,
+ },
+ });
}
return (
-
- Signup
-
- {
- signupRequest.request({
- body: {
- ...formValues,
- type: AUTH_TYPE,
- channel: AUTH_CHANNEL,
- },
- });
- })}>
-
- {signupRequest.error?.type !== 'validation' && (
-
- )}
-
- Signup
+ {signupRequest.error?.type !== 'validation' && (
+
+ )}
+
+
+ (
+
+ First Name
+
+
+
+
+
+ )}
/>
- (
+
+ Last Name
+
+
+
+
+
+ )}
/>
- (
+
+ Email
+
+
+
+
+
+ )}
/>
-
- (
+
+ Phone
+
+
+ field.onChange(normalizePhone(e.target.value))
+ }
+ onBlur={field.onBlur}
+ name={field.name}
+ />
+
+
+
+ )}
/>
-
{AUTH_TYPE === 'password' && (
- (
+
+ Password
+
+
+
+
+
+ )}
/>
)}
-
+
+ {loading && }
Signup
-
+
Already have an account?{' '}
-
+
Login
-
-
+
+
{}}
+ onAuthStart={() => {}}
onError={onAuthError}
/>
-
-
+
+
);
}
diff --git a/services/web/src/screens/Error/index.js b/services/web/src/screens/Error/index.js
index c8b2e5a10..ff4d5063a 100644
--- a/services/web/src/screens/Error/index.js
+++ b/services/web/src/screens/Error/index.js
@@ -1,4 +1,3 @@
-import { Alert, Button } from '@mantine/core';
import PropTypes from 'prop-types';
import BasicLayout from 'layouts/Basic';
@@ -6,6 +5,9 @@ import { useSession } from 'stores/session';
import Meta from 'components/Meta';
+import { Alert, AlertTitle, AlertDescription } from '@/components/ui/alert';
+import { Button } from '@/components/ui/button';
+
import { ENV_NAME } from 'utils/env';
function ErrorScreen({ title = 'Something went wrong', error }) {
@@ -49,11 +51,12 @@ function ErrorScreen({ title = 'Something went wrong', error }) {
return (
-
- {renderErrorBody()}
+
+ {title}
+ {renderErrorBody()}
-
-
+
+
Logout
diff --git a/services/web/src/screens/Invites/Actions.js b/services/web/src/screens/Invites/Actions.js
index 3193b7dfd..2d835341a 100644
--- a/services/web/src/screens/Invites/Actions.js
+++ b/services/web/src/screens/Invites/Actions.js
@@ -1,6 +1,3 @@
-import { ActionIcon, Menu, Text } from '@mantine/core';
-import { notifications, showNotification } from '@mantine/notifications';
-
import {
PiDotsThreeOutlineVerticalBold,
PiRepeatBold,
@@ -9,20 +6,29 @@ import {
import Confirm from 'modals/Confirm';
+import { Button } from '@/components/ui/button';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+
import { request, useRequest } from 'utils/api';
+import { notify } from 'utils/notify';
export default function InviteActions({ invite, reload }) {
const resentRequest = useRequest({
method: 'POST',
path: `/1/invites/${invite.id}/resend`,
onSuccess: () => {
- showNotification({
+ notify({
title: 'Invite re-sent',
color: 'green',
});
},
onError: (error) => {
- notifications.show({
+ notify({
title: 'Failed to re-send invite',
message: error.message,
color: 'red',
@@ -31,21 +37,21 @@ export default function InviteActions({ invite, reload }) {
});
return (
-
-
-
+
+
+
-
-
+
+
-
- {
+
+ {
resentRequest.request();
- }}
- leftSection={ }>
+ }}>
+
Resend Invite
-
+
+
Are you sure you want to delete {invite.email} ?
-
+
}
trigger={
- }>
+ e.preventDefault()}>
+
Delete
-
+
}
/>
-
-
+
+
);
}
diff --git a/services/web/src/screens/Invites/Form.js b/services/web/src/screens/Invites/Form.js
index aca0aee30..d2d5df8a7 100644
--- a/services/web/src/screens/Invites/Form.js
+++ b/services/web/src/screens/Invites/Form.js
@@ -1,86 +1,138 @@
-import { Button, NativeSelect, Textarea } from '@mantine/core';
-import { useState } from 'react';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
import ErrorMessage from 'components/ErrorMessage';
import { useModalContext } from 'components/ModalWrapper';
-import Actions from 'components/form-fields/Actions';
-import { useFields } from 'hooks/forms';
-import { useRequest } from 'hooks/request';
-import { request } from 'utils/api';
+import { Button } from '@/components/ui/button';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select';
+import { Spinner } from '@/components/ui/spinner';
+import { Textarea } from '@/components/ui/textarea';
-export default function InviteForm(props) {
- const { close } = useModalContext();
+import { useRequest } from 'utils/api';
- const [input, setInput] = useState('');
+const ROLES = [
+ { value: 'viewer', label: 'Viewer' },
+ { value: 'admin', label: 'Admin' },
+ { value: 'superAdmin', label: 'Super Admin' },
+];
- const { fields, setField } = useFields({
- role: 'viewer',
- });
+const schema = z.object({
+ // Accept a comma/newline separated string and validate each address, passing
+ // a clean array of emails on to the request.
+ emails: z
+ .string()
+ .min(1, 'Enter at least one email address.')
+ .transform((value) =>
+ value
+ .split(/[\s,]+/)
+ .map((email) => email.trim())
+ .filter(Boolean),
+ )
+ .pipe(
+ z
+ .array(z.string().email('Enter valid email addresses.'))
+ .min(1, 'Enter at least one email address.'),
+ ),
+ role: z.enum(['viewer', 'admin', 'superAdmin']),
+});
+
+export default function InviteForm({ onSuccess }) {
+ const { close } = useModalContext();
- const { run, loading, error } = useRequest(async (body) => {
- await request({
- method: 'POST',
- path: '/1/invites',
- body,
- });
- await props.onSuccess?.();
- close();
+ const form = useForm({
+ resolver: zodResolver(schema),
+ defaultValues: {
+ emails: '',
+ role: 'viewer',
+ },
});
- function onSubmit(evt) {
- evt.preventDefault();
- run(fields);
- }
+ const { request, error } = useRequest({
+ method: 'POST',
+ path: '/1/invites',
+ onSuccess: async () => {
+ await onSuccess?.();
+ close();
+ },
+ });
- function onEmailsBlur() {
- setField({
- name: 'emails',
- value: input.trim().split(/,\s+/),
- });
+ async function onSubmit(body) {
+ await request({ body });
}
return (
-
-
+
+
+
+
+ (
+
+ Emails
+
+
+
+
+
+ )}
+ />
- {
- setInput(evt.target.value);
- }}
- onBlur={onEmailsBlur}
- placeholder="Enter email addresses separated by comma or new line."
- />
+ (
+
+ Role
+
+
+
+
+
+
+
+ {ROLES.map((role) => (
+
+ {role.label}
+
+ ))}
+
+
+
+
+ )}
+ />
-
-
-
- Invite Members
-
-
-
+
+
+ {form.formState.isSubmitting && }
+ Invite Members
+
+
+
+
);
}
diff --git a/services/web/src/screens/Invites/List.js b/services/web/src/screens/Invites/List.js
index 2084fc9dc..5c6b3308a 100644
--- a/services/web/src/screens/Invites/List.js
+++ b/services/web/src/screens/Invites/List.js
@@ -1,11 +1,19 @@
-import { Badge, Button, Group, Stack, Table, Text } from '@mantine/core';
-
import ErrorMessage from 'components/ErrorMessage';
import ModalWrapper from 'components/ModalWrapper';
import PageHeader from 'components/PageHeader';
import Search from 'components/Search';
import SearchFilters from 'components/Search/Filters';
+import { Badge } from '@/components/ui/badge';
+import { Button } from '@/components/ui/button';
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHeader,
+ TableRow,
+} from '@/components/ui/table';
+
import { request } from 'utils/api';
import { formatDateTime } from 'utils/date';
@@ -22,109 +30,93 @@ export default function Invites() {
}
return (
- <>
-
- {({ items, reload, error }) => {
- return (
-
- }
- trigger={Invite User }
- />
- }
- />
+
+ {({ items, reload, error }) => {
+ return (
+
+
}
+ trigger={
Invite User }
+ />
+ }
+ />
-
-
-
-
+
-
-
-
-
-
+
-
-
-
-
-
- Email
- Status
-
- Invited At
-
- Actions
-
-
-
-
-
-
-
- No invites found.
-
-
-
-
- {items.map((item) => {
- return (
-
- {item.email}
-
-
- {item.status}
-
-
- {formatDateTime(item.createdAt)}
-
+
+
+
+ Email
+ Status
+
+ Invited At
+
+
+ Actions
+
+
+
+
+
+
+
+
+ No invites found.
+
+
+
+
+ {items.map((item) => {
+ return (
+
+ {item.email}
+
+ {item.status}
+
+ {formatDateTime(item.createdAt)}
+
+
-
-
- );
- }}
-
- >
+
+
+
+ );
+ })}
+
+
+
+
+
+ );
+ }}
+
);
}
diff --git a/services/web/src/screens/Loading/index.js b/services/web/src/screens/Loading/index.js
index d888ab13b..3a7bd855e 100644
--- a/services/web/src/screens/Loading/index.js
+++ b/services/web/src/screens/Loading/index.js
@@ -1,16 +1,17 @@
-import { Center, Loader } from '@mantine/core';
import React from 'react';
import Meta from 'components/Meta';
+import { Spinner } from '@/components/ui/spinner';
+
export default class LoadingScreen extends React.Component {
render() {
return (
<>
-
-
-
+
+
+
>
);
}
diff --git a/services/web/src/screens/Lockout.js b/services/web/src/screens/Lockout.js
index 96c6ef80d..caf678614 100644
--- a/services/web/src/screens/Lockout.js
+++ b/services/web/src/screens/Lockout.js
@@ -1,11 +1,13 @@
import { Link, useNavigate } from '@bedrockio/router';
-import { Button, Center, Group, Paper, Stack, Text } from '@mantine/core';
import { useEffect } from 'react';
import { useSession } from 'stores/session';
import Meta from 'components/Meta';
+import { Button } from '@/components/ui/button';
+import { Card, CardContent } from '@/components/ui/card';
+
function Lockout() {
const navigate = useNavigate();
const { isLoggedIn } = useSession();
@@ -17,23 +19,23 @@ function Lockout() {
}, [isLoggedIn, navigate]);
return (
-
+
-
-
-
+
+
+
Your account is pending approval. Please wait for an administrator
to assign the necessary permissions/roles before you can access the
dashboard.
-
-
-
- Logout
+
+
+
+ Logout
-
-
-
-
+
+
+
+
);
}
diff --git a/services/web/src/screens/Onboard/index.js b/services/web/src/screens/Onboard/index.js
index 0d1441b93..d06b106ec 100644
--- a/services/web/src/screens/Onboard/index.js
+++ b/services/web/src/screens/Onboard/index.js
@@ -1,7 +1,10 @@
import { Redirect } from '@bedrockio/router';
-import { Button, Paper, Stack, TextInput } from '@mantine/core';
+import { zodResolver } from '@hookform/resolvers/zod';
import { pick, startCase } from 'lodash';
+import { Loader2 } from 'lucide-react';
import { useState } from 'react';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
import { useSession } from 'stores/session';
@@ -10,6 +13,17 @@ import Logo from 'components/Logo';
import Meta from 'components/Meta';
import PhoneField from 'components/form-fields/Phone';
+import { Button } from '@/components/ui/button';
+import { Card, CardContent } from '@/components/ui/card';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from '@/components/ui/form';
+import { Input } from '@/components/ui/input';
+
import { request } from 'utils/api';
const FIELDS = [
@@ -19,18 +33,24 @@ const FIELDS = [
},
];
+const schema = z.object({
+ email: z.string().optional(),
+ phone: z.string().min(1, `${startCase('phone')} is required.`),
+});
+
export default function OnboardScreen() {
const { user, updateUser } = useSession();
- const [body, setBody] = useState(() => {
- return pick(
+ const form = useForm({
+ resolver: zodResolver(schema),
+ defaultValues: pick(
user,
FIELDS.map((f) => f.name),
- );
+ ),
});
const [error, setError] = useState(null);
- const [loading, setLoading] = useState(false);
+ const loading = form.formState.isSubmitting;
function validateFields(user) {
for (let field of FIELDS) {
@@ -49,17 +69,9 @@ export default function OnboardScreen() {
}
}
- function setField(evt, { name, value }) {
- setBody({
- ...body,
- [name]: value,
- });
- }
-
- async function onSubmit() {
+ async function onSubmit(body) {
try {
setError(null);
- setLoading(true);
validateFields(body);
@@ -72,7 +84,6 @@ export default function OnboardScreen() {
updateUser(data);
} catch (error) {
setError(error);
- setLoading(false);
}
}
@@ -81,47 +92,58 @@ export default function OnboardScreen() {
}
return (
-
+
-
{
- e.preventDefault();
- onSubmit();
- }}
- noValidate>
-
-
- {error?.type !== 'validation' && }
- {!user.email && (
-
- )}
- {!user.phone && (
-
- )}
-
- Continue
-
-
-
-
-
+
+
+
+
+ {error?.type !== 'validation' && }
+ {!user.email && (
+ (
+
+
+
+
+
+
+ )}
+ />
+ )}
+ {!user.phone && (
+ (
+
+
+ field.onChange(value)}
+ />
+
+
+
+ )}
+ />
+ )}
+
+ {loading && }
+ Continue
+
+
+
+
+
+
);
}
diff --git a/services/web/src/screens/Organizations/Actions.js b/services/web/src/screens/Organizations/Actions.js
index fd99c81fc..d096b7faf 100644
--- a/services/web/src/screens/Organizations/Actions.js
+++ b/services/web/src/screens/Organizations/Actions.js
@@ -1,5 +1,4 @@
import { Link } from '@bedrockio/router';
-import { ActionIcon, Button, Group, Menu, Text } from '@mantine/core';
import {
PiCode,
@@ -13,6 +12,14 @@ import Protected from 'components/Protected';
import Confirm from 'modals/Confirm';
import InspectObject from 'modals/InspectObject';
+import { Button } from '@/components/ui/button';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+
import { request } from 'utils/api';
export default function OrganizationActions({
@@ -24,31 +31,24 @@ export default function OrganizationActions({
if (displayMode === 'list') {
return (
-
-
-
+
+
+
+
+
);
} else if (displayMode === 'edit') {
return (
-
- Back
+
+ Back
);
} else if (displayMode === 'show') {
return (
-
- Edit
+
+ Edit
);
@@ -56,36 +56,36 @@ export default function OrganizationActions({
}
return (
-
+
{renderButton()}
-
-
- {displayMode !== 'list' ? (
-
-
-
- ) : (
-
-
-
- )}
-
+
+
+
+
+
+
-
+
}>Inspect}
+ trigger={
+ e.preventDefault()}>
+
+ Inspect
+
+ }
/>
- }>
- Audit Logs
-
+
+
+
+ Audit Logs
+
+
+
Are you sure you want to delete{' '}
{organization.name} ?
-
+
}
trigger={
- }>
+ e.preventDefault()}>
+
Delete
-
+
}
/>
-
-
-
+
+
+
);
}
diff --git a/services/web/src/screens/Organizations/Detail/Edit.js b/services/web/src/screens/Organizations/Detail/Edit.js
index ce138afd2..c6cd59e86 100644
--- a/services/web/src/screens/Organizations/Detail/Edit.js
+++ b/services/web/src/screens/Organizations/Detail/Edit.js
@@ -1,5 +1,4 @@
import { useNavigate } from '@bedrockio/router';
-import { Stack } from '@mantine/core';
import { useContext } from 'react';
import { PageContext } from 'stores/page';
@@ -12,7 +11,7 @@ export default function OrganizationOverview() {
const navigate = useNavigate();
return (
-
+
-
+
);
}
diff --git a/services/web/src/screens/Organizations/Detail/Overview.js b/services/web/src/screens/Organizations/Detail/Overview.js
index c4bd3f600..85d1aeaa2 100644
--- a/services/web/src/screens/Organizations/Detail/Overview.js
+++ b/services/web/src/screens/Organizations/Detail/Overview.js
@@ -1,7 +1,10 @@
-import { Stack, Table, Text } from '@mantine/core';
-
import { usePage } from 'stores/page';
+import {
+ DefinitionItem,
+ DefinitionList,
+} from '@/components/ui/definition-list';
+
import { formatDateTime } from 'utils/date';
import Menu from './Menu';
@@ -12,23 +15,17 @@ export default function ShopOverview() {
<>
-
-
- {organization.name}
-
-
-
-
- Created At
- {formatDateTime(organization.createdAt)}
-
-
- Updated At
- {formatDateTime(organization.updatedAt)}
-
-
-
-
+
+
{organization.name}
+
+
+ {formatDateTime(organization.createdAt)}
+
+
+ {formatDateTime(organization.updatedAt)}
+
+
+
>
);
}
diff --git a/services/web/src/screens/Organizations/Form.js b/services/web/src/screens/Organizations/Form.js
index 226e6b782..03ca64369 100644
--- a/services/web/src/screens/Organizations/Form.js
+++ b/services/web/src/screens/Organizations/Form.js
@@ -1,10 +1,33 @@
-import { Box, Button, Fieldset, Grid, Stack, TextInput } from '@mantine/core';
-import { useForm } from '@mantine/form';
-import { showNotification } from '@mantine/notifications';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
import ErrorMessage from 'components/ErrorMessage';
+import { Button } from '@/components/ui/button';
+import {
+ Card,
+ CardContent,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import { Input } from '@/components/ui/input';
+import { Spinner } from '@/components/ui/spinner';
+
import { useRequest } from 'utils/api';
+import { notify } from 'utils/notify';
+
+const schema = z.object({
+ name: z.string().min(1, 'Name is required'),
+});
/**
* Organization form component for creating or updating an organization
@@ -18,7 +41,8 @@ function OrganizationForm({ organization, onSuccess = () => {} }) {
const isUpdate = !!organization;
const form = useForm({
- initialValues: organization || {
+ resolver: zodResolver(schema),
+ defaultValues: organization || {
name: '',
},
});
@@ -34,48 +58,54 @@ function OrganizationForm({ organization, onSuccess = () => {} }) {
path: '/1/organizations',
}),
onSuccess: ({ data }) => {
- showNotification({
+ notify({
title: isUpdate
- ? `${form.values.name} was successfully updated.`
- : `${form.values.name} was successfully created.`,
+ ? `${form.getValues('name')} was successfully updated.`
+ : `${form.getValues('name')} was successfully created.`,
color: 'green',
});
onSuccess(data);
},
});
+ function onSubmit(values) {
+ return editRequest.request({ body: values });
+ }
+
return (
- <>
-
- editRequest.request({ body: values }),
- )}>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ Organization Details
+
+
+ (
+
+ Name
+
+
+
+
+
+ )}
+ />
+
+
+
+
+
+
+ {editRequest.loading && }
{isUpdate ? 'Update' : 'Create'} Organization
-
+
- >
+
);
}
diff --git a/services/web/src/screens/Organizations/List.js b/services/web/src/screens/Organizations/List.js
index 2c255a5fd..3f98f340c 100644
--- a/services/web/src/screens/Organizations/List.js
+++ b/services/web/src/screens/Organizations/List.js
@@ -1,20 +1,19 @@
import { Link } from '@bedrockio/router';
-import {
- Anchor,
- Button,
- Group,
- Loader,
- Stack,
- Table,
- Text,
-} from '@mantine/core';
-
import ErrorMessage from 'components/ErrorMessage';
import PageHeader from 'components/PageHeader';
import Search from 'components/Search';
import SearchFilters from 'components/Search/Filters';
+import { Button } from '@/components/ui/button';
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHeader,
+ TableRow,
+} from '@/components/ui/table';
+
import { request } from 'utils/api';
import { formatDateTime } from 'utils/date';
@@ -30,98 +29,88 @@ export default function OrganizationList() {
}
return (
- <>
-
- {({ items: organizations, reload, error, loading }) => (
-
-
-
- New Organization
-
- >
- }
- />
+
+ {({ items: organizations, reload, error }) => (
+
+
+ New Organization
+
+ }
+ />
-
-
-
-
-
- {loading && }
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
+
-
-
-
- Name
-
- Created
-
-
- Actions
-
-
-
-
-
-
-
-
- No organization found.
-
-
-
-
- {organizations.map((organization) => (
-
-
-
- {organization.name}
-
-
-
- {formatDateTime(organization.createdAt)}
-
-
+
+
+
+ Name
+
+ Created
+
+
+ Actions
+
+
+
+
+
+
+
+
+ No organization found.
+
+
+
+
+ {organizations.map((organization) => (
+
+
+
+ {organization.name}
+
+
+ {formatDateTime(organization.createdAt)}
+
+
-
-
- )}
-
- >
+
+
+
+ ))}
+
+
+
+
+
+ )}
+
);
}
diff --git a/services/web/src/screens/Organizations/New.js b/services/web/src/screens/Organizations/New.js
index d0d2a8659..e2f3118cd 100644
--- a/services/web/src/screens/Organizations/New.js
+++ b/services/web/src/screens/Organizations/New.js
@@ -1,15 +1,16 @@
import { Link, useNavigate } from '@bedrockio/router';
-import { Button, Stack } from '@mantine/core';
import PageHeader from 'components/PageHeader';
+import { Button } from '@/components/ui/button';
+
import Form from './Form';
export default function NewOrganization() {
const navigate = useNavigate();
return (
-
+
- Back
+
+ Back
}
/>
@@ -28,6 +29,6 @@ export default function NewOrganization() {
navigate(`/organizations`);
}}
/>
-
+
);
}
diff --git a/services/web/src/screens/Products/Actions.js b/services/web/src/screens/Products/Actions.js
index ce1747f53..944b40135 100644
--- a/services/web/src/screens/Products/Actions.js
+++ b/services/web/src/screens/Products/Actions.js
@@ -1,5 +1,4 @@
import { Link } from '@bedrockio/router';
-import { ActionIcon, Button, Group, Menu, Text } from '@mantine/core';
import {
PiCode,
@@ -12,6 +11,14 @@ import Protected from 'components/Protected';
import Confirm from 'modals/Confirm';
import InspectObject from 'modals/InspectObject';
+import { Button } from '@/components/ui/button';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+
import { request } from 'utils/api';
export default function ProductsActions({
@@ -23,31 +30,24 @@ export default function ProductsActions({
if (displayMode === 'list') {
return (
-
-
-
+
+
+
+
+
);
} else if (displayMode === 'edit') {
return (
-
- Back
+
+ Back
);
} else if (displayMode === 'show') {
return (
-
- Edit
+
+ Edit
);
@@ -55,34 +55,34 @@ export default function ProductsActions({
}
return (
-
+
{renderButton()}
-
-
- {displayMode !== 'list' ? (
-
-
-
- ) : (
-
-
-
- )}
-
+
+
+
+
+
+
-
+
}>Inspect}
+ trigger={
+ e.preventDefault()}>
+
+ Inspect
+
+ }
/>
- }>
- Audit Logs
-
+
+
+
+ Audit Logs
+
+
+
Are you sure you want to delete{' '}
{product.name} ?
-
+
}
confirmButton="Delete"
trigger={
- }>
+ e.preventDefault()}>
+
Delete
-
+
}
/>
-
-
-
+
+
+
);
}
diff --git a/services/web/src/screens/Products/Detail/Edit.js b/services/web/src/screens/Products/Detail/Edit.js
index c8b03ebf8..daf62ce36 100644
--- a/services/web/src/screens/Products/Detail/Edit.js
+++ b/services/web/src/screens/Products/Detail/Edit.js
@@ -1,5 +1,4 @@
import { useNavigate } from '@bedrockio/router';
-import { Stack } from '@mantine/core';
import { usePage } from 'stores/page';
@@ -11,7 +10,7 @@ export default function EditProduct() {
const navigate = useNavigate();
return (
-
+
-
+
);
}
diff --git a/services/web/src/screens/Products/Detail/Overview.js b/services/web/src/screens/Products/Detail/Overview.js
index 6d29e5010..f1db3907d 100644
--- a/services/web/src/screens/Products/Detail/Overview.js
+++ b/services/web/src/screens/Products/Detail/Overview.js
@@ -1,8 +1,12 @@
-import { Group, Image, Stack, Table, Text, Title } from '@mantine/core';
-
import { usePage } from 'stores/page';
import ArrayList from 'components/ArrayList';
+import Thumbnail from 'components/Thumbnail';
+
+import {
+ DefinitionItem,
+ DefinitionList,
+} from '@/components/ui/definition-list';
import { formatCurrency } from 'utils/currency';
import { formatDateTime } from 'utils/date';
@@ -16,48 +20,34 @@ export default function ShopOverview() {
<>
-
-
- {product.description}
-
- Images
-
+
+
{product.description}
+
Images
+
{product.images.map((image) => (
-
))}
-
+
-
-
-
- Price
-
- {formatCurrency(product.priceUsd || 0, 'USD')}
-
-
-
- Selling Points
-
-
-
-
-
- Created At
- {formatDateTime(product.createdAt)}
-
-
- Updated At
- {formatDateTime(product.updatedAt)}
-
-
-
-
+
+
+ {formatCurrency(product.priceUsd || 0, 'USD')}
+
+
+
+
+
+ {formatDateTime(product.createdAt)}
+
+
+ {formatDateTime(product.updatedAt)}
+
+
+
>
);
}
diff --git a/services/web/src/screens/Products/Form.js b/services/web/src/screens/Products/Form.js
index 45ee22d0a..297338ec7 100644
--- a/services/web/src/screens/Products/Form.js
+++ b/services/web/src/screens/Products/Form.js
@@ -1,31 +1,60 @@
-import {
- Button,
- Checkbox,
- Fieldset,
- Grid,
- Group,
- NumberInput,
- Stack,
- TagsInput,
- TextInput,
- Textarea,
-} from '@mantine/core';
-
-import { DateTimePicker } from '@mantine/dates';
-import { useForm } from '@mantine/form';
-import { showNotification } from '@mantine/notifications';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useState } from 'react';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
import ErrorMessage from 'components/ErrorMessage';
import SearchDropdown from 'components/SearchDropdown';
+import CurrencyField from 'components/form-fields/Currency';
+import DateTimeField from 'components/form-fields/DateTime';
+import TagsField from 'components/form-fields/Tags';
import UploadsField from 'components/form-fields/Uploads';
+import { Button } from '@/components/ui/button';
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
+import { Checkbox } from '@/components/ui/checkbox';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import { Input } from '@/components/ui/input';
+import { Spinner } from '@/components/ui/spinner';
+import { Textarea } from '@/components/ui/textarea';
+
import { useRequest } from 'utils/api';
+import { notifySuccess } from 'utils/notify';
-function parseProduct(product) {
+const schema = z.object({
+ name: z.string().min(1, 'Name is required'),
+ description: z.string().optional(),
+ isFeatured: z.boolean().optional(),
+ priceUsd: z.union([z.number(), z.string()]).nullable().optional(),
+ expiresAt: z.any().nullable().optional(),
+ sellingPoints: z.array(z.string()).optional(),
+ images: z.array(z.any()).optional(),
+ shop: z.any().nullable().optional(),
+});
+
+function getDefaultValues(product, shop) {
+ if (product) {
+ return {
+ ...product,
+ expiresAt: product.expiresAt ? new Date(product.expiresAt) : null,
+ };
+ }
return {
- ...product,
- // the DateTimePicker is very strict about getting an date object
- expiresAt: product?.expiresAt ? new Date(product.expiresAt) : null,
+ name: '',
+ description: '',
+ isFeatured: false,
+ priceUsd: '',
+ expiresAt: null,
+ sellingPoints: [],
+ images: [],
+ shop: shop || null,
};
}
@@ -33,18 +62,12 @@ export default function ProductForm({ product, shop, onSuccess = () => {} }) {
const isUpdate = !!product;
const form = useForm({
- initialValues: parseProduct(product) || {
- name: '',
- description: '',
- isFeatured: false,
- priceUsd: '',
- expiresAt: null,
- sellingPoints: [],
- images: [],
- shop: shop || null,
- },
+ resolver: zodResolver(schema),
+ defaultValues: getDefaultValues(product, shop),
});
+ const [uploadError, setUploadError] = useState(null);
+
const editRequest = useRequest({
...(isUpdate
? {
@@ -55,15 +78,14 @@ export default function ProductForm({ product, shop, onSuccess = () => {} }) {
method: 'POST',
path: '/1/products',
body: {
- shop: shop?.id || form.values.shop?.id,
+ shop: shop?.id || form.getValues('shop')?.id,
},
}),
onSuccess: ({ data }) => {
- showNotification({
+ notifySuccess({
title: isUpdate
- ? `${form.values.name} was successfully updated.`
- : `${form.values.name} was successfully created.`,
- color: 'green',
+ ? `${form.getValues('name')} was successfully updated.`
+ : `${form.getValues('name')} was successfully created.`,
});
setTimeout(() => {
onSuccess(data);
@@ -71,92 +93,178 @@ export default function ProductForm({ product, shop, onSuccess = () => {} }) {
},
});
- const handleSellingPointsChange = (values) => {
- form.setFieldValue('sellingPoints', values);
- };
+ async function onSubmit(values) {
+ setUploadError(null);
+ await editRequest.request({ body: values });
+ }
return (
-
- editRequest.request({ body: values }),
- )}>
-
-
-
-
-
-
-
-
-
-
-
- ({
- value,
- label: value,
- })) || []
- }
- value={form.values.sellingPoints || []}
- onChange={handleSellingPointsChange}
+
+
+
+
+
+ Product Details
+
+
+ (
+
+
+ Name
+ *
+
+
+
+
+
+
+ )}
+ />
+ (
+
+ Description
+
+
+
+
+
+ )}
+ />
+ (
+
+
+
+
+ Is Featured
+
+
+ )}
+ />
+ (
+
+
+ field.onChange(value)}
+ />
+
+
+
+ )}
+ />
+ (
+
+
+ field.onChange(value)}
+ />
+
+
+
+ )}
+ />
+ (
+
+
+ field.onChange(value)}
+ />
+
+
+
+ )}
+ />
+ {!shop && (
+ (
+
+
+
+
+
+
+ )}
/>
- {!shop && (
-
+ )}
+
+
+
+
+ Product Images
+
+
+ (
+
+
+ field.onChange(value)}
+ onError={(error) => setUploadError(error)}
+ />
+
+
+
)}
-
-
-
-
-
- editRequest.setError(error)}
/>
-
-
-
-
-
-
+
+
+
+
+
+
+ {form.formState.isSubmitting && }
{isUpdate ? 'Update' : 'Create'} Product
-
-
-
+
+
+
);
}
diff --git a/services/web/src/screens/Products/List.js b/services/web/src/screens/Products/List.js
index bd2798ce1..524e3a3fc 100644
--- a/services/web/src/screens/Products/List.js
+++ b/services/web/src/screens/Products/List.js
@@ -1,20 +1,19 @@
import { Link } from '@bedrockio/router';
-import {
- Anchor,
- Button,
- Group,
- Image,
- Loader,
- Stack,
- Table,
- Text,
-} from '@mantine/core';
-
import ErrorMessage from 'components/ErrorMessage';
import PageHeader from 'components/PageHeader';
import Search from 'components/Search';
import SearchFilters from 'components/Search/Filters';
+import Thumbnail from 'components/Thumbnail';
+
+import { Button } from '@/components/ui/button';
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHeader,
+ TableRow,
+} from '@/components/ui/table';
import { request } from 'utils/api';
import { formatUsd } from 'utils/currency';
@@ -34,128 +33,110 @@ export default function ProductList() {
return (
- {({ items: products, reload, error, loading }) => {
+ {({ items: products, reload, error }) => {
return (
-
+
-
-
- New Product
+
+ New Product
>
}
/>
-
-
-
-
-
-
-
-
-
- {loading && }
-
+
-
-
-
+
+
+
Name
Image
Price
Created
-
- Actions
-
-
-
-
+ Actions
+
+
+
-
-
-
+
+
+
No products found.
-
-
-
+
+
+
{products.map((product) => {
const [image] = product.images;
return (
-
-
-
+
+
{product.name}
-
-
-
+
+
+
{image && (
-
)}
-
- {formatUsd(product.priceUsd)}
- {formatDateTime(product.createdAt)}
-
-
-
-
+
+ {formatUsd(product.priceUsd)}
+ {formatDateTime(product.createdAt)}
+
+
+
+
);
})}
-
+
+
-
+
);
}}
diff --git a/services/web/src/screens/Products/New.js b/services/web/src/screens/Products/New.js
index c36c427c1..79b17b059 100644
--- a/services/web/src/screens/Products/New.js
+++ b/services/web/src/screens/Products/New.js
@@ -1,15 +1,16 @@
import { Link, useNavigate } from '@bedrockio/router';
-import { Button, Stack } from '@mantine/core';
import PageHeader from 'components/PageHeader';
+import { Button } from '@/components/ui/button';
+
import Form from './Form';
export default function NewShop() {
const navigate = useNavigate();
return (
-
+
- Back
+
+ Back
}
/>
@@ -28,6 +29,6 @@ export default function NewShop() {
navigate(`/products/${product.id}`);
}}
/>
-
+
);
}
diff --git a/services/web/src/screens/Settings/Details.js b/services/web/src/screens/Settings/Details.js
index 35460bb87..b6a94ece9 100644
--- a/services/web/src/screens/Settings/Details.js
+++ b/services/web/src/screens/Settings/Details.js
@@ -1,7 +1,7 @@
-import { Button, Fieldset, Stack, TextInput } from '@mantine/core';
-import { useForm } from '@mantine/form';
-import { notifications } from '@mantine/notifications';
+import { zodResolver } from '@hookform/resolvers/zod';
import { pick } from 'lodash';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
import { useSession } from 'stores/session';
@@ -9,15 +9,36 @@ import ErrorMessage from 'components/ErrorMessage';
import Meta from 'components/Meta';
import PhoneField from 'components/form-fields/Phone';
+import { Button } from '@/components/ui/button';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import { Input } from '@/components/ui/input';
+import { Spinner } from '@/components/ui/spinner';
+
import { useRequest } from 'utils/api';
+import { notify } from 'utils/notify';
import Menu from './Menu';
+const schema = z.object({
+ firstName: z.string().optional(),
+ lastName: z.string().optional(),
+ phone: z.string().nullable().optional(),
+ email: z.string().optional(),
+});
+
function Profile() {
const { user, meta, updateUser } = useSession();
const form = useForm({
- initialValues: {
+ resolver: zodResolver(schema),
+ defaultValues: {
...pick(user, ['id', 'firstName', 'lastName', 'phone', 'email']),
notifications: meta.notifications.map((base) => {
const config = user.notifications.find((c) => {
@@ -40,7 +61,7 @@ function Profile() {
path: `/1/users/me`,
onSuccess: ({ data }) => {
updateUser(data);
- notifications.show({
+ notify({
title: 'Profile updated',
message: 'Your profile has been successfully updated.',
color: 'green',
@@ -48,53 +69,96 @@ function Profile() {
},
});
+ function onSubmit(values) {
+ saveRequest.request({
+ body: {
+ ...values,
+ },
+ });
+ }
+
return (
-
+
-
{
- saveRequest.request({
- body: {
- ...values,
- },
- });
- })}>
-
-
-
-
- {user.phone && (
-
+
+
+ Profile
+
+
-
- Update Profile
-
-
-
+
+ {saveRequest.loading && }
+ Update Profile
+
+
+
+
);
}
diff --git a/services/web/src/screens/Settings/Notifications.js b/services/web/src/screens/Settings/Notifications.js
index b3dc3dd1a..6ea006de2 100644
--- a/services/web/src/screens/Settings/Notifications.js
+++ b/services/web/src/screens/Settings/Notifications.js
@@ -1,14 +1,20 @@
-import { Button, Chip, Fieldset, Group, Stack, Text } from '@mantine/core';
-import { useForm } from '@mantine/form';
-import { notifications } from '@mantine/notifications';
+import { zodResolver } from '@hookform/resolvers/zod';
import { pick } from 'lodash';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
import { useSession } from 'stores/session';
import ErrorMessage from 'components/ErrorMessage';
import Meta from 'components/Meta';
+import { Button } from '@/components/ui/button';
+import { Checkbox } from '@/components/ui/checkbox';
+import { Label } from '@/components/ui/label';
+import { Spinner } from '@/components/ui/spinner';
+
import { useRequest } from 'utils/api';
+import { notify } from 'utils/notify';
import Menu from './Menu';
@@ -27,11 +33,14 @@ const CHANNELS = [
},
];
+const schema = z.object({}).passthrough();
+
function Notifications() {
const { user, meta, updateUser } = useSession();
const form = useForm({
- initialValues: {
+ resolver: zodResolver(schema),
+ defaultValues: {
...pick(user, ['id', 'firstName', 'lastName', 'phone', 'email']),
notifications: meta.notifications.map((base) => {
const config = user.notifications.find((c) => {
@@ -54,7 +63,7 @@ function Notifications() {
path: `/1/users/me`,
onSuccess: ({ data }) => {
updateUser(data);
- notifications.show({
+ notify({
title: 'Profile updated',
message: 'Your profile has been successfully updated.',
color: 'green',
@@ -62,56 +71,60 @@ function Notifications() {
},
});
+ function onSubmit(values) {
+ saveRequest.request({
+ body: {
+ ...values,
+ },
+ });
+ }
+
+ const notificationsValue = form.watch('notifications') || [];
+
return (
-
+
-
{
- saveRequest.request({
- body: {
- ...values,
- },
- });
- })}>
-
- {form.getValues().notifications.map((notification, index) => {
+
+
+ Notifications
+ {notificationsValue.map((notification, index) => {
const { name, label } = notification;
return (
-
- {label}
-
+
+
{label}
+
{CHANNELS.map((channel) => {
+ const fieldName = `notifications.${index}.${channel.value}`;
+ const id = `${name}-${channel.value}`;
return (
-
- {channel.label}
-
+
+ {
+ form.setValue(fieldName, checked === true);
+ }}
+ />
+ {channel.label}
+
);
})}
-
-
+
+
);
})}
-
-
+
+
+ {saveRequest.loading && }
Update Profile
-
+
);
}
diff --git a/services/web/src/screens/Settings/Security/Sessions.js b/services/web/src/screens/Settings/Security/Sessions.js
index dde638d23..d4e4914dc 100644
--- a/services/web/src/screens/Settings/Security/Sessions.js
+++ b/services/web/src/screens/Settings/Security/Sessions.js
@@ -1,22 +1,23 @@
-import {
- ActionIcon,
- Badge,
- Button,
- Divider,
- Group,
- Stack,
- Table,
- Text,
-} from '@mantine/core';
-
-import { notifications } from '@mantine/notifications';
import { PiTrash } from 'react-icons/pi';
import { useSession } from 'stores/session';
+import { Badge } from '@/components/ui/badge';
+import { Button } from '@/components/ui/button';
+import { Separator } from '@/components/ui/separator';
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from '@/components/ui/table';
+
import { getToken, useRequest } from 'utils/api';
import countries from 'utils/countries';
import { fromNow } from 'utils/date';
+import { notify } from 'utils/notify';
import { parseToken } from 'utils/token';
import { parseUserAgent } from 'utils/user-agent';
@@ -32,7 +33,7 @@ export default function Sessions() {
bootstrap();
},
onError: () => {
- notifications.show({
+ notify({
position: 'top-right',
title: 'Error',
message: 'Failed to logout session(s)',
@@ -42,17 +43,17 @@ export default function Sessions() {
});
return (
-
-
-
-
- Device/Browser
- Country
- Last Used
-
-
-
-
+
+
+
+
+ Device/Browser
+ Country
+ Last Used
+
+
+
+
{user.authTokens.map((token) => {
const country = countries.find(
(country) => country.countryCode === token.country,
@@ -61,54 +62,50 @@ export default function Sessions() {
const { device, os, browser } = parseUserAgent(token.userAgent);
return (
-
-
-
-
+
+
+
+ className="text-sm">
{[os, browser].join(' - ')}
-
+
{token.jti === jti && (
-
- Current
-
+
Current
)}
-
-
-
+
+
+
{country?.nameEn || 'N/A'}
-
- {fromNow(token.lastUsedAt)}
-
-
+ {fromNow(token.lastUsedAt)}
+
+
logoutRequest.request({ body: { jti: token.jti } })
}>
-
-
-
-
+
+
+
+
);
})}
-
+
-
-
+
+
logoutRequest.request({ body: { all: true } })}>
Logout All Sessions
-
-
+
+
);
}
diff --git a/services/web/src/screens/Settings/Security/TwoFactorAuthentication.js b/services/web/src/screens/Settings/Security/TwoFactorAuthentication.js
index 96709cbff..8fbd8ce68 100644
--- a/services/web/src/screens/Settings/Security/TwoFactorAuthentication.js
+++ b/services/web/src/screens/Settings/Security/TwoFactorAuthentication.js
@@ -1,16 +1,39 @@
-import { Button, Group, Select, Stack, Text } from '@mantine/core';
-import { notifications } from '@mantine/notifications';
+import { useState } from 'react';
import { useSession } from 'stores/session';
import Authenticator from 'components/Authenticator';
-import ModalWrapper from 'components/ModalWrapper';
+
+import { Button } from '@/components/ui/button';
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog';
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select';
import { useRequest } from 'utils/api';
+import { notify } from 'utils/notify';
+
+const METHODS = [
+ { label: 'None', value: 'none' },
+ { label: 'SMS', value: 'sms' },
+ { label: 'Email', value: 'email' },
+ { label: 'Authenticator', value: 'totp' },
+];
export default function Sessions() {
const { user, updateUser } = useSession();
+ const [authenticatorOpen, setAuthenticatorOpen] = useState(false);
+
const mfaRequest = useRequest({
method: 'PATCH',
path: '/1/auth/mfa-method',
@@ -20,7 +43,7 @@ export default function Sessions() {
authenticators: data.authenticators,
});
- notifications.show({
+ notify({
position: 'top-right',
title: 'Success',
message:
@@ -31,7 +54,7 @@ export default function Sessions() {
});
},
onError: (error) => {
- notifications.show({
+ notify({
position: 'top-right',
title: 'Error',
message: error.message,
@@ -50,7 +73,7 @@ export default function Sessions() {
});
},
onError: (error) => {
- notifications.show({
+ notify({
position: 'top-right',
title: 'Error',
message: error.message,
@@ -61,23 +84,7 @@ export default function Sessions() {
function onMfaMethodChange(value) {
if (value === 'totp' && !hasTotp) {
- return (
- {
- notifications.show({
- position: 'top-right',
- title: 'Success',
- message: 'Two-factor authentication enabled.',
- color: 'green',
- });
- }}
- />
- }
- />
- );
+ setAuthenticatorOpen(true);
} else {
mfaRequest.request({
body: {
@@ -92,35 +99,55 @@ export default function Sessions() {
);
return (
-
- Select how you want to verify your identity
+
+
Select how you want to verify your identity
+ onValueChange={onMfaMethodChange}>
+
+
+
+
+ {METHODS.map((method) => (
+
+ {method.label}
+
+ ))}
+
+
{hasTotp && user.mfaMethod === 'totp' && (
-
+
{
removeTotpRequest.request();
}}
- loading={removeTotpRequest.loading}
- disabled={removeTotpRequest.loading}
- color="red">
+ disabled={removeTotpRequest.loading}>
Reset Authenticator Configuration
-
+
)}
-
+
+
+
+
+ Enable Authenticator
+
+ setAuthenticatorOpen(false)}
+ onSuccess={() => {
+ notify({
+ position: 'top-right',
+ title: 'Success',
+ message: 'Two-factor authentication enabled.',
+ color: 'green',
+ });
+ }}
+ />
+
+
+
);
}
diff --git a/services/web/src/screens/Settings/Security/index.js b/services/web/src/screens/Settings/Security/index.js
index 6f54752b4..2cad86aee 100644
--- a/services/web/src/screens/Settings/Security/index.js
+++ b/services/web/src/screens/Settings/Security/index.js
@@ -1,15 +1,3 @@
-import {
- ActionIcon,
- Button,
- Divider,
- Fieldset,
- Grid,
- Group,
- LoadingOverlay,
- Stack,
- Text,
-} from '@mantine/core';
-
import { useState } from 'react';
import { PiTrashBold } from 'react-icons/pi';
@@ -20,6 +8,10 @@ import GoogleDisableButton from 'components/Auth/Google/DisableButton';
import ErrorMessage from 'components/ErrorMessage';
import Meta from 'components/Meta';
+import { Button } from '@/components/ui/button';
+import { Separator } from '@/components/ui/separator';
+import { Spinner } from '@/components/ui/spinner';
+
import { createPasskey, removePasskey } from 'utils/auth/passkey';
import { formatDate, fromNow } from 'utils/date';
@@ -108,89 +100,92 @@ export default function Security() {
const { loading, error } = state;
return (
-
+
-
-
-
-
-
-
-
- {user.authenticators
- .filter((authenticator) => authenticator.type === 'passkey')
- .map((passkey) => {
- const { id, name, createdAt, lastUsedAt } = passkey;
- return (
-
-
- {name}
-
- Added on {formatDate(createdAt)} | Last used{' '}
- {fromNow(lastUsedAt)}
-
-
- deletePasskey(passkey)}>
-
-
-
- );
- })}
-
-
- Add Passkey
-
-
-
-
-
-
-
-
-
-
-
- Google
-
-
- {hasAuthenticator('google') ? (
-
- ) : (
- Sign in with Google to enable.
- )}
-
-
-
-
-
- Apple
-
-
- {hasAuthenticator('apple') ? (
-
- ) : (
-
Sign in with Apple to enable.
- )}
+
+ {loading && (
+
+
+
+ )}
+
+
+
+ Passkey
+
+ {user.authenticators
+ .filter((authenticator) => authenticator.type === 'passkey')
+ .map((passkey) => {
+ const { id, name, createdAt, lastUsedAt } = passkey;
+ return (
+
+
+ {name}
+
+ Added on {formatDate(createdAt)} | Last used{' '}
+ {fromNow(lastUsedAt)}
+
+
+
deletePasskey(passkey)}>
+
+
+
+ );
+ })}
+
+
+ Add Passkey
+
-
-
-
-
-
+
+
+
+
+ Two-factor authentication
+
+
+
+
+ Sign-in with
+
+
+ Google
+
+ {hasAuthenticator('google') ? (
+
+ ) : (
+
Sign in with Google to enable.
+ )}
+
+
+
+
+ Apple
+
+ {hasAuthenticator('apple') ? (
+
+ ) : (
+
Sign in with Apple to enable.
+ )}
+
+
+
+
+
+ Sessions
-
-
-
+
+
+
-
+
);
}
diff --git a/services/web/src/screens/Shops/Actions.js b/services/web/src/screens/Shops/Actions.js
index 1373613d4..f0defe33b 100644
--- a/services/web/src/screens/Shops/Actions.js
+++ b/services/web/src/screens/Shops/Actions.js
@@ -1,5 +1,4 @@
import { Link } from '@bedrockio/router';
-import { ActionIcon, Button, Group, Menu, Text } from '@mantine/core';
import {
PiCode,
@@ -13,6 +12,14 @@ import Protected from 'components/Protected';
import Confirm from 'modals/Confirm';
import InspectObject from 'modals/InspectObject';
+import { Button } from '@/components/ui/button';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+
import { request } from 'utils/api';
export default function ShopsActions({ shop, reload, displayMode = 'show' }) {
@@ -20,28 +27,24 @@ export default function ShopsActions({ shop, reload, displayMode = 'show' }) {
if (displayMode === 'list') {
return (
-
-
-
+
+
+
+
+
);
} else if (displayMode === 'edit') {
return (
-
- Back
+
+ Back
);
} else if (displayMode === 'show') {
return (
-
- Edit
+
+ Edit
);
@@ -49,34 +52,34 @@ export default function ShopsActions({ shop, reload, displayMode = 'show' }) {
}
return (
-
+
{renderButton()}
-
-
- {displayMode !== 'list' ? (
-
-
-
- ) : (
-
-
-
- )}
-
+
+
+
+
+
+
-
+
}>Inspect}
+ trigger={
+ e.preventDefault()}>
+
+ Inspect
+
+ }
/>
- }>
- Audit Logs
-
+
+
+
+ Audit Logs
+
+
@@ -91,20 +94,23 @@ export default function ShopsActions({ shop, reload, displayMode = 'show' }) {
reload();
}}
content={
-
+
Are you sure you want to delete {shop.name} ?
-
+
}
confirmButton="Delete"
trigger={
- }>
+ e.preventDefault()}>
+
Delete
-
+
}
/>
-
-
-
+
+
+
);
}
diff --git a/services/web/src/screens/Shops/Detail/Edit.js b/services/web/src/screens/Shops/Detail/Edit.js
index 1c617ea67..066bce199 100644
--- a/services/web/src/screens/Shops/Detail/Edit.js
+++ b/services/web/src/screens/Shops/Detail/Edit.js
@@ -1,5 +1,4 @@
import { useNavigate } from '@bedrockio/router';
-import { Stack } from '@mantine/core';
import { usePage } from 'stores/page';
@@ -11,7 +10,7 @@ export default function EditShop() {
const navigate = useNavigate();
return (
-
+
-
+
);
}
diff --git a/services/web/src/screens/Shops/Detail/Overview.js b/services/web/src/screens/Shops/Detail/Overview.js
index 9e154bb28..8467dfbe3 100644
--- a/services/web/src/screens/Shops/Detail/Overview.js
+++ b/services/web/src/screens/Shops/Detail/Overview.js
@@ -1,7 +1,12 @@
-import { Group, Image, Stack, Table, Text, Title } from '@mantine/core';
-
import { usePage } from 'stores/page';
+import Thumbnail from 'components/Thumbnail';
+
+import {
+ DefinitionItem,
+ DefinitionList,
+} from '@/components/ui/definition-list';
+
import { formatDateTime } from 'utils/date';
import { formatAddress } from 'utils/formatting';
import { urlForUpload } from 'utils/uploads';
@@ -14,50 +19,38 @@ export default function ShopOverview() {
<>
-
-
- {shop.description}
-
- Images
-
+
+
{shop.description}
+
Images
+
{shop.images.map((image) => (
-
))}
-
+
-
-
-
- Categories
-
-
- {shop.categories.map((category) => {
- return {category.name} ;
- })}
-
-
-
-
- Address
- {formatAddress(shop.address)}
-
-
- Created At
- {formatDateTime(shop.createdAt)}
-
-
- Updated At
- {formatDateTime(shop.updatedAt)}
-
-
-
-
+
+
+
+ {shop.categories.map((category) => {
+ return {category.name} ;
+ })}
+
+
+
+ {formatAddress(shop.address)}
+
+
+ {formatDateTime(shop.createdAt)}
+
+
+ {formatDateTime(shop.updatedAt)}
+
+
+
>
);
}
diff --git a/services/web/src/screens/Shops/Detail/Products.js b/services/web/src/screens/Shops/Detail/Products.js
index bf3823be2..ca8c1fc4c 100644
--- a/services/web/src/screens/Shops/Detail/Products.js
+++ b/services/web/src/screens/Shops/Detail/Products.js
@@ -1,13 +1,22 @@
import { Link } from '@bedrockio/router';
-import { Anchor, Box, Group, Image, Loader, Table, Text } from '@mantine/core';
import { usePage } from 'stores/page';
import ErrorMessage from 'components/ErrorMessage';
import Search from 'components/Search';
import SearchFilters from 'components/Search/Filters';
+import Thumbnail from 'components/Thumbnail';
import Actions from 'screens/Products/Actions';
+import { Spinner } from '@/components/ui/spinner';
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHeader,
+ TableRow,
+} from '@/components/ui/table';
+
import { request } from 'utils/api';
import { formatUsd } from 'utils/currency';
import { formatDateTime } from 'utils/date';
@@ -36,20 +45,22 @@ export default function ShopProducts() {
{({ items: products, reload, loading, error }) => {
return (
-
-
- {loading && }
-
+
+
+
+ {loading && }
+
+
-
-
+
+
-
-
-
+
+
+
Name
Image
Price
@@ -57,49 +68,46 @@ export default function ShopProducts() {
Created
Actions
-
-
-
+
+
+
-
-
-
+
+
+
No products found.
-
-
-
+
+
+
{products.map((product) => (
-
-
-
+
+
{product.name}
-
-
-
-
+
+
+
-
- {formatUsd(product.priceUsd)}
- {formatDateTime(product.createdAt)}
-
+
+ {formatUsd(product.priceUsd)}
+ {formatDateTime(product.createdAt)}
+
-
-
+
+
))}
-
+
-
+
);
}}
diff --git a/services/web/src/screens/Shops/Form.js b/services/web/src/screens/Shops/Form.js
index bcbca28be..e690514d0 100644
--- a/services/web/src/screens/Shops/Form.js
+++ b/services/web/src/screens/Shops/Form.js
@@ -1,23 +1,40 @@
-import {
- Button,
- Fieldset,
- Grid,
- Group,
- Select,
- Stack,
- TextInput,
- Textarea,
-} from '@mantine/core';
-
-import { useForm } from '@mantine/form';
-import { showNotification } from '@mantine/notifications';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
import ErrorMessage from 'components/ErrorMessage';
import SearchDropdown from 'components/SearchDropdown';
import UploadsField from 'components/form-fields/Uploads';
+import { Button } from '@/components/ui/button';
+import {
+ Card,
+ CardContent,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import { Input } from '@/components/ui/input';
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select';
+import { Spinner } from '@/components/ui/spinner';
+import { Textarea } from '@/components/ui/textarea';
+
import { useRequest } from 'utils/api';
import allCountries from 'utils/countries';
+import { notify } from 'utils/notify';
const countries = allCountries.map(({ countryCode, nameEn }) => ({
value: countryCode,
@@ -25,11 +42,25 @@ const countries = allCountries.map(({ countryCode, nameEn }) => ({
key: countryCode,
}));
+const schema = z.object({
+ name: z.string().min(1, 'Name is required'),
+ description: z.string().optional(),
+ categories: z.array(z.any()).optional(),
+ images: z.array(z.any()).optional(),
+ address: z.object({
+ line1: z.string().optional(),
+ line2: z.string().optional(),
+ city: z.string().optional(),
+ countryCode: z.string().optional(),
+ }),
+});
+
export default function ShopForm({ shop, onSuccess = () => {} }) {
const isUpdate = !!shop;
const form = useForm({
- initialValues: shop || {
+ resolver: zodResolver(schema),
+ defaultValues: shop || {
name: '',
description: '',
categories: [],
@@ -54,7 +85,7 @@ export default function ShopForm({ shop, onSuccess = () => {} }) {
path: '/1/shops',
}),
onSuccess: ({ data }) => {
- showNotification({
+ notify({
title: isUpdate
? `${data.name} was successfully updated.`
: `${data.name} was successfully created.`,
@@ -66,74 +97,190 @@ export default function ShopForm({ shop, onSuccess = () => {} }) {
},
});
+ async function onSubmit(values) {
+ await editRequest.request({ body: values });
+ }
+
return (
-
- editRequest.request({ body: values }),
- )}>
-
-
-
-
-
-
-
+
+
+
+
+
+
+ {form.formState.isSubmitting && (
+
+ )}
+ {isUpdate ? 'Update' : 'Create New'} Shop
+
+
+
+
+
);
}
diff --git a/services/web/src/screens/Shops/List.js b/services/web/src/screens/Shops/List.js
index d74a6e985..b96f23af8 100644
--- a/services/web/src/screens/Shops/List.js
+++ b/services/web/src/screens/Shops/List.js
@@ -1,21 +1,20 @@
import { Link } from '@bedrockio/router';
-import {
- Anchor,
- Button,
- Group,
- Image,
- Loader,
- Stack,
- Table,
- Text,
-} from '@mantine/core';
-
import ErrorMessage from 'components/ErrorMessage';
import PageHeader from 'components/PageHeader';
import Protected from 'components/Protected';
import Search from 'components/Search';
import SearchFilters from 'components/Search/Filters';
+import Thumbnail from 'components/Thumbnail';
+
+import { Button } from '@/components/ui/button';
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHeader,
+ TableRow,
+} from '@/components/ui/table';
import { request } from 'utils/api';
import allCountries from 'utils/countries';
@@ -58,141 +57,123 @@ export default function ShopList() {
}
return (
- <>
-
- {({ items: shops, reload, error, loading }) => {
- return (
-
-
-
-
-
- New Shop
-
-
- >
- }
- />
+
+ {({ items: shops, reload, error }) => {
+ return (
+
+
+
+
+
+ New Shop
+
+
+ >
+ }
+ />
-
-
-
-
-
-
-
- {loading && }
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
+
-
-
-
- Name
- Image
-
- Created
-
-
- Actions
-
-
-
-
-
-
-
-
- No shops found.
-
-
-
-
- {shops.map((shop) => {
- return (
-
-
-
- {shop.name}
-
-
-
- {shop.images.length > 0 && (
-
- )}
-
- {formatDateTime(shop.createdAt)}
-
+
+
+
+ Name
+ Image
+
+ Created
+
+
+ Actions
+
+
+
+
+
+
+
+
+ No shops found.
+
+
+
+
+ {shops.map((shop) => {
+ return (
+
+
+
+ {shop.name}
+
+
+
+ {shop.images.length > 0 && (
+
+ )}
+
+ {formatDateTime(shop.createdAt)}
+
+
-
-
- );
- }}
-
- >
+
+
+
+ );
+ })}
+
+
+
+
+
+ );
+ }}
+
);
}
diff --git a/services/web/src/screens/Shops/New.js b/services/web/src/screens/Shops/New.js
index 44e26c387..86c143eda 100644
--- a/services/web/src/screens/Shops/New.js
+++ b/services/web/src/screens/Shops/New.js
@@ -1,15 +1,16 @@
import { Link, useNavigate } from '@bedrockio/router';
-import { Button, Stack } from '@mantine/core';
import PageHeader from 'components/PageHeader';
+import { Button } from '@/components/ui/button';
+
import Form from './Form';
export default function NewShop() {
const navigate = useNavigate();
return (
-
+
- Back
+
+ Back
}
/>
@@ -31,6 +32,6 @@ export default function NewShop() {
navigate('/shops');
}}
/>
-
+
);
}
diff --git a/services/web/src/screens/Templates/Actions.js b/services/web/src/screens/Templates/Actions.js
index e8da24642..2af92f559 100644
--- a/services/web/src/screens/Templates/Actions.js
+++ b/services/web/src/screens/Templates/Actions.js
@@ -1,5 +1,4 @@
import { Link } from '@bedrockio/router';
-import { ActionIcon, Group, Menu, Text } from '@mantine/core';
import {
PiCode,
@@ -13,41 +12,55 @@ import Protected from 'components/Protected';
import Confirm from 'modals/Confirm';
import InspectObject from 'modals/InspectObject';
+import { Button } from '@/components/ui/button';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+
import { request } from 'utils/api';
export default function TemplatesActions(props) {
const { template, reload } = props;
return (
-
-
-
-
+
+
+
+
-
-
+
+
-
+
}>Inspect}
+ trigger={
+ e.preventDefault()}>
+
+ Inspect
+
+ }
/>
- }>
- Edit
-
+
+
+
+ Edit
+
+
- }>
- Audit Logs
-
+
+
+
+ Audit Logs
+
+
@@ -62,21 +75,24 @@ export default function TemplatesActions(props) {
reload();
}}
content={
-
+
Are you sure you want to delete{' '}
{template.name} ?
-
+
}
confirmButton="Delete"
trigger={
- }>
+ e.preventDefault()}>
+
Delete
-
+
}
/>
-
-
-
+
+
+
);
}
diff --git a/services/web/src/screens/Templates/Detail/Content.js b/services/web/src/screens/Templates/Detail/Content.js
index 6bbd088c3..1cc31df81 100644
--- a/services/web/src/screens/Templates/Detail/Content.js
+++ b/services/web/src/screens/Templates/Detail/Content.js
@@ -1,12 +1,3 @@
-import {
- Button,
- Group,
- Paper,
- SegmentedControl,
- Space,
- Textarea,
-} from '@mantine/core';
-
import React, { useState } from 'react';
import {
@@ -17,7 +8,6 @@ import {
PiQuestionBold,
} from 'react-icons/pi';
-import { showSuccessNotification } from 'helpers/notifications';
import { usePage } from 'stores/page';
import ErrorMessage from 'components/ErrorMessage';
@@ -25,7 +15,15 @@ import Actions from 'components/form-fields/Actions';
import { useFields } from 'hooks/forms';
import { useRequest } from 'hooks/request';
+import { Button } from '@/components/ui/button';
+import { Card } from '@/components/ui/card';
+import { Label } from '@/components/ui/label';
+import { Spinner } from '@/components/ui/spinner';
+import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
+import { Textarea } from '@/components/ui/textarea';
+
import { request } from 'utils/api';
+import { notifySuccess } from 'utils/notify';
import HelpModal from './HelpModal';
import Menu from './Menu';
@@ -62,7 +60,7 @@ export default function Content() {
template: data,
});
- showSuccessNotification({
+ notifySuccess({
message: 'Updated Content',
});
});
@@ -76,29 +74,31 @@ export default function Content() {
return (
-
+
{renderChannelSelector()}
-
+
+
+ {CHANNEL_LABELS[channel]}
+
+
+
-
+
-
+
}>
+
+
Help
}
@@ -107,20 +107,19 @@ export default function Content() {
}>
+
+
Params
}
/>
-
-
+
+
-
+
+ {loading && }
Save
@@ -132,18 +131,19 @@ export default function Content() {
if (template.channels.length > 1) {
return (
- {
- const Icon = CHANNEL_ICONS[channel];
- return {
- value: channel,
- label: ,
- };
- })}
- />
-
+
+
+ {template.channels.map((channel) => {
+ const Icon = CHANNEL_ICONS[channel];
+ return (
+
+
+
+ );
+ })}
+
+
+
);
}
diff --git a/services/web/src/screens/Templates/Detail/HelpModal.js b/services/web/src/screens/Templates/Detail/HelpModal.js
index 3730f615e..96124e1ef 100644
--- a/services/web/src/screens/Templates/Detail/HelpModal.js
+++ b/services/web/src/screens/Templates/Detail/HelpModal.js
@@ -1,77 +1,80 @@
-import { Alert, Tabs, Text, Typography } from '@mantine/core';
-
import Code from 'components/Code';
import ModalWrapper from 'components/ModalWrapper';
+import { Alert, AlertDescription } from '@/components/ui/alert';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
+
function HelpModal() {
function render() {
return (
-
- Markdown
- Helpers
-
- {renderMarkdown()}
- {renderHelpers()}
+
+ Markdown
+ Helpers
+
+ {renderMarkdown()}
+ {renderHelpers()}
);
}
function renderMarkdown() {
return (
-
- Emphasis
+
+
Emphasis
{Markdown.Emphasis}
- Lists
+ Lists
{Markdown.Lists}
- Headings
+ Headings
{Markdown.Headings}
- Line Break
+ Line Break
{Markdown.LineBreak}
-
+
);
}
function renderHelpers() {
return (
-
- Link Helpers
+
+
Link Helpers
{Helpers.Link}
-
Button Helpers
+
Button Helpers
{Helpers.Button}
-
Image Helpers
+
Image Helpers
{Helpers.Image}
-
Date/Time Helpers
+
Date/Time Helpers
{Helpers.Date}
-
Relative Time Helpers
+
Relative Time Helpers
{Helpers.Relative}
-
Metadata
+
Metadata
{Helpers.Metadata}
-
-
- Note that quotes are optional except when the value starts with{' '}
- {'{{'}
-
+
+
+
+ Note that quotes are optional except when the value starts with{' '}
+ {'{{'}
+
+
- Conditional Rendering
+ Conditional Rendering
{Helpers.Conditional}
- Looping (Simple)
+ Looping (Simple)
{Helpers.LoopingSimple}
- Looping (List)
+ Looping (List)
{Helpers.LoopingList}
- Looping (Table)
+ Looping (Table)
{Helpers.LoopingTable}
-
+
);
}
diff --git a/services/web/src/screens/Templates/Detail/Menu.js b/services/web/src/screens/Templates/Detail/Menu.js
index 043a31c01..2230607eb 100644
--- a/services/web/src/screens/Templates/Detail/Menu.js
+++ b/services/web/src/screens/Templates/Detail/Menu.js
@@ -1,5 +1,4 @@
import { Link } from '@bedrockio/router';
-import { Button } from '@mantine/core';
import React from 'react';
import { PiPencilSimpleBold } from 'react-icons/pi';
@@ -8,6 +7,8 @@ import { usePage } from 'stores/page';
import PageHeader from 'components/PageHeader';
import Protected from 'components/Protected';
+import { Button } from '@/components/ui/button';
+
import Actions from '../Actions';
export default function TemplateMenu({ displayMode }) {
@@ -54,12 +55,11 @@ export default function TemplateMenu({ displayMode }) {
rightSection={
- }
- component={Link}
- to={`/templates/${template.id}/edit`}>
- Edit
+
+
+ Edit
+
+
diff --git a/services/web/src/screens/Templates/Detail/Overview.js b/services/web/src/screens/Templates/Detail/Overview.js
index 4f9a41511..df06199fa 100644
--- a/services/web/src/screens/Templates/Detail/Overview.js
+++ b/services/web/src/screens/Templates/Detail/Overview.js
@@ -1,7 +1,12 @@
-import { Badge, Group, Stack, Table } from '@mantine/core';
-
import { usePage } from 'stores/page';
+import { Badge } from '@/components/ui/badge';
+import { Card, CardContent } from '@/components/ui/card';
+import {
+ DefinitionItem,
+ DefinitionList,
+} from '@/components/ui/definition-list';
+
import { formatDateTime } from 'utils/date';
import Menu from './Menu';
@@ -11,30 +16,31 @@ export default function Overview() {
return (
<>
-
-
-
-
- Channels
-
-
+
+
+
+
+
+
{template.channels.map((channel) => {
- return
{channel} ;
+ return (
+
+ {channel}
+
+ );
})}
-
-
-
-
- Created At
- {formatDateTime(template.createdAt)}
-
-
- Updated At
- {formatDateTime(template.updatedAt)}
-
-
-
-
+
+
+
+ {formatDateTime(template.createdAt)}
+
+
+ {formatDateTime(template.updatedAt)}
+
+
+
+
+
>
);
}
diff --git a/services/web/src/screens/Templates/Detail/ParamsModal.js b/services/web/src/screens/Templates/Detail/ParamsModal.js
index 40af053d2..4d302b2f5 100644
--- a/services/web/src/screens/Templates/Detail/ParamsModal.js
+++ b/services/web/src/screens/Templates/Detail/ParamsModal.js
@@ -1,8 +1,9 @@
-import { Alert, Paper } from '@mantine/core';
-
import ModalWrapper from 'components/ModalWrapper';
import { useLoader } from 'hooks/loader';
+import { Alert, AlertDescription } from '@/components/ui/alert';
+import { Card } from '@/components/ui/card';
+
import { request } from 'utils/api';
function ParamsModal(props) {
@@ -19,15 +20,17 @@ function ParamsModal(props) {
});
return (
-
-
- Note that this is dummy data for template creation and not what will
- actually be sent.
+
+
+
+ Note that this is dummy data for template creation and not what will
+ actually be sent.
+
{JSON.stringify(params, null, 2)}
-
+
);
}
diff --git a/services/web/src/screens/Templates/Detail/Preview.js b/services/web/src/screens/Templates/Detail/Preview.js
index 9dba3c912..db43b39aa 100644
--- a/services/web/src/screens/Templates/Detail/Preview.js
+++ b/services/web/src/screens/Templates/Detail/Preview.js
@@ -1,10 +1,13 @@
-import { Alert, Group, Loader, Paper, Stack } from '@mantine/core';
import { useEffect, useState } from 'react';
import { usePage } from 'stores/page';
import ErrorMessage from 'components/ErrorMessage';
+import { Alert, AlertDescription } from '@/components/ui/alert';
+import { Card } from '@/components/ui/card';
+import { Spinner } from '@/components/ui/spinner';
+
import { request } from 'utils/api';
import Menu from './Menu';
@@ -46,10 +49,14 @@ export default function Preview() {
return (
<>
-
+
- {message &&
{message} }
-
+ {message && (
+
+ {message}
+
+ )}
+
{loading && (
-
+
)}
-
-
+
+
-
-
+
+
>
);
}
diff --git a/services/web/src/screens/Templates/Detail/SendPreviewButton.js b/services/web/src/screens/Templates/Detail/SendPreviewButton.js
index 71e0a8086..461511b1a 100644
--- a/services/web/src/screens/Templates/Detail/SendPreviewButton.js
+++ b/services/web/src/screens/Templates/Detail/SendPreviewButton.js
@@ -1,6 +1,7 @@
-import { Button } from '@mantine/core';
import { PiPaperPlaneTiltBold } from 'react-icons/pi';
+import { Button } from '@/components/ui/button';
+
import SendPreviewModal from './SendPreviewModal';
export default function SendPreviewButton(props) {
@@ -8,7 +9,8 @@ export default function SendPreviewButton(props) {
}>
+
+
Test
}
diff --git a/services/web/src/screens/Templates/Detail/SendPreviewModal.js b/services/web/src/screens/Templates/Detail/SendPreviewModal.js
index 872c4ba66..736b8fafb 100644
--- a/services/web/src/screens/Templates/Detail/SendPreviewModal.js
+++ b/services/web/src/screens/Templates/Detail/SendPreviewModal.js
@@ -1,7 +1,5 @@
-import { Alert, Button, Stack } from '@mantine/core';
import { useState } from 'react';
-import { showSuccessNotification } from 'helpers/notifications';
import { useSession } from 'stores/session';
import ErrorMessage from 'components/ErrorMessage';
@@ -10,7 +8,12 @@ import SearchDropdown from 'components/SearchDropdown';
import EmailField from 'components/form-fields/Email';
import PhoneField from 'components/form-fields/Phone';
+import { Alert, AlertDescription } from '@/components/ui/alert';
+import { Button } from '@/components/ui/button';
+import { Spinner } from '@/components/ui/spinner';
+
import { request } from 'utils/api';
+import { notifySuccess } from 'utils/notify';
function SendPreviewModal(props) {
const { channel, template } = props;
@@ -30,7 +33,7 @@ function SendPreviewModal(props) {
}
});
- function setField(evt, { name, value }) {
+ function setField(name, value) {
setFields({
...fields,
[name]: value,
@@ -52,7 +55,7 @@ function SendPreviewModal(props) {
},
});
setLoading(false);
- showSuccessNotification({
+ notifySuccess({
message: 'Test message sent.',
});
@@ -79,7 +82,7 @@ function SendPreviewModal(props) {
name="email"
label="Email"
value={fields.email || ''}
- onChange={setField}
+ onChange={(evt) => setField('email', evt.target.value)}
/>
);
}
@@ -100,24 +103,30 @@ function SendPreviewModal(props) {
setField('userId', value)}
/>
);
}
return (
-
+
{renderField()}
-
Dummy data will be used to populate objects.
-
+
+
+ Dummy data will be used to populate objects.
+
+
+
+ {loading && }
Send
-
+
);
}
diff --git a/services/web/src/screens/Templates/Form.js b/services/web/src/screens/Templates/Form.js
index 4fd29e790..e1477270d 100644
--- a/services/web/src/screens/Templates/Form.js
+++ b/services/web/src/screens/Templates/Form.js
@@ -1,50 +1,94 @@
import { useNavigate } from '@bedrockio/router';
-import { Button, Paper, Stack, TextInput } from '@mantine/core';
-
-import { showSuccessNotification } from 'helpers/notifications';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useState } from 'react';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
import ErrorMessage from 'components/ErrorMessage';
import Actions from 'components/form-fields/Actions';
import ChipsField from 'components/form-fields/Chips';
-import { useFields } from 'hooks/forms';
-import { useRequest } from 'hooks/request';
+
+import { Button } from '@/components/ui/button';
+import { Card, CardContent } from '@/components/ui/card';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import { Input } from '@/components/ui/input';
+import { Spinner } from '@/components/ui/spinner';
import { request } from 'utils/api';
+import { notifySuccess } from 'utils/notify';
+
+const CHANNEL_OPTIONS = [
+ {
+ label: 'Email',
+ value: 'email',
+ },
+ {
+ label: 'SMS',
+ value: 'sms',
+ },
+ {
+ label: 'Push',
+ value: 'push',
+ },
+];
+
+const schema = z.object({
+ name: z.string().min(1, 'Name is required'),
+ channels: z.array(z.string()).optional(),
+});
export default function TemplateForm(props) {
const { template, onSuccess } = props;
- const { fields, setField } = useFields(template);
const navigate = useNavigate();
- const { run, loading, error } = useRequest(async (body) => {
- let result;
- if (template) {
- const { data } = await request({
- method: 'PATCH',
- path: `/1/templates/${template.id}`,
- body,
- });
- result = data;
- } else {
- const { data } = await request({
- method: 'POST',
- path: '/1/templates',
- body,
- });
- result = data;
- }
+ const form = useForm({
+ resolver: zodResolver(schema),
+ defaultValues: {
+ name: template?.name || '',
+ channels: template?.channels || [],
+ },
+ });
- showSuccessNotification({
- message: 'Added template',
- });
+ const [error, setError] = useState(null);
+ const loading = form.formState.isSubmitting;
- onSuccess?.(result);
- });
+ async function onSubmit(body) {
+ try {
+ setError(null);
+
+ let result;
+ if (template) {
+ const { data } = await request({
+ method: 'PATCH',
+ path: `/1/templates/${template.id}`,
+ body,
+ });
+ result = data;
+ } else {
+ const { data } = await request({
+ method: 'POST',
+ path: '/1/templates',
+ body,
+ });
+ result = data;
+ }
- function onSubmit(evt) {
- evt.preventDefault();
- run(fields);
+ notifySuccess({
+ message: 'Added template',
+ });
+
+ onSuccess?.(result);
+ } catch (error) {
+ setError(error);
+ }
}
function onCancelClick() {
@@ -52,51 +96,60 @@ export default function TemplateForm(props) {
}
return (
-
-
-
-
-
-
-
-
-
-
- Cancel
-
-
- {template ? 'Update' : 'Create'}
-
-
-
+
+
+
+
+
+
+ (
+
+ Name
+
+
+
+
+
+ )}
+ />
+ (
+
+
+ field.onChange(value)}
+ />
+
+
+
+ )}
+ />
+
+
+
+
+
+ Cancel
+
+
+ {loading && }
+ {template ? 'Update' : 'Create'}
+
+
+
+
);
}
diff --git a/services/web/src/screens/Templates/List.js b/services/web/src/screens/Templates/List.js
index 4534cf248..f751a3003 100644
--- a/services/web/src/screens/Templates/List.js
+++ b/services/web/src/screens/Templates/List.js
@@ -1,22 +1,21 @@
import { Link } from '@bedrockio/router';
-import {
- Anchor,
- Badge,
- Button,
- Group,
- Loader,
- Stack,
- Table,
- Text,
-} from '@mantine/core';
-
import ErrorMessage from 'components/ErrorMessage';
import PageHeader from 'components/PageHeader';
import Protected from 'components/Protected';
import Search from 'components/Search';
import SearchFilters from 'components/Search/Filters';
+import { Badge } from '@/components/ui/badge';
+import { Button } from '@/components/ui/button';
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHeader,
+ TableRow,
+} from '@/components/ui/table';
+
import { request } from 'utils/api';
import Actions from './Actions';
@@ -31,112 +30,99 @@ export default function TemplateList() {
}
return (
- <>
-
- {({ items: templates, reload, error, loading }) => {
- return (
-
-
-
-
- New Template
-
-
- >
- }
- />
+
+ {({ items: templates, reload, error }) => {
+ return (
+
+
+
+ New Template
+
+
+ }
+ />
+
+
+
+
+
-
-
-
-
- {loading && }
+
+
+
+
+
-
-
-
-
-
+
-
+
+
+
+ Name
+ Channels
+
+ Actions
+
+
+
+
+
+
+
+
+ No templates found.
+
+
+
+
+ {templates.map((template) => {
+ return (
+
+
+
+ {template.name}
+
+
+
+
+ {template.channels.map((channel) => {
+ return (
+
+ {channel}
+
+ );
+ })}
+
+
+
+
+
+
+ );
+ })}
+
+
-
-
-
-
- Name
- Channels
-
- Actions
-
-
-
-
-
-
-
-
- No templates found.
-
-
-
-
- {templates.map((template) => {
- return (
-
-
-
- {template.name}
-
-
-
-
- {template.channels.map((channel) => {
- return {channel} ;
- })}
-
-
-
-
-
-
- );
- })}
-
-
-
-
-
- );
- }}
-
- >
+
+
+ );
+ }}
+
);
}
diff --git a/services/web/src/screens/Templates/New.js b/services/web/src/screens/Templates/New.js
index 67163a5e7..e4e9f56ee 100644
--- a/services/web/src/screens/Templates/New.js
+++ b/services/web/src/screens/Templates/New.js
@@ -1,5 +1,4 @@
import { useNavigate } from '@bedrockio/router';
-import { Space } from '@mantine/core';
import BackLink from 'components/BackLink';
@@ -11,7 +10,7 @@ export default function NewTemplate() {
return (
<>
-
+
{
navigate(`/templates/${template.id}`);
diff --git a/services/web/src/screens/Unsubscribe.js b/services/web/src/screens/Unsubscribe.js
index 712baf086..8078f4b28 100644
--- a/services/web/src/screens/Unsubscribe.js
+++ b/services/web/src/screens/Unsubscribe.js
@@ -1,11 +1,13 @@
import { useQuery } from '@bedrockio/router';
-import { Alert, Group, LoadingOverlay, Stack } from '@mantine/core';
import { useEffect, useState } from 'react';
import ErrorMessage from 'components/ErrorMessage';
import Logo from 'components/Logo';
import Meta from 'components/Meta';
+import { Alert, AlertTitle, AlertDescription } from '@/components/ui/alert';
+import { Spinner } from '@/components/ui/spinner';
+
import { request } from 'utils/api';
export default function Unsubscribe() {
@@ -44,17 +46,24 @@ export default function Unsubscribe() {
function render() {
return (
-
- {loading && }
-
+
+ {loading && (
+
+
+
+ )}
+
-
+
{success && (
-
You have been unsubscribed.
+
+ Success
+ You have been unsubscribed.
+
)}
-
-
+
+
);
}
diff --git a/services/web/src/screens/Users/Actions.js b/services/web/src/screens/Users/Actions.js
index 087cc4f17..a97e9d8a0 100644
--- a/services/web/src/screens/Users/Actions.js
+++ b/services/web/src/screens/Users/Actions.js
@@ -1,5 +1,4 @@
import { Link, useNavigate } from '@bedrockio/router';
-import { ActionIcon, Button, Group, Menu, Text } from '@mantine/core';
import {
PiCode,
@@ -18,6 +17,14 @@ import Confirm from 'modals/Confirm';
import InspectObject from 'modals/InspectObject';
import LoginAsUser from 'modals/LoginAsUser';
+import { Button } from '@/components/ui/button';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+
import { request } from 'utils/api';
export default function UserActions(props) {
@@ -40,28 +47,24 @@ export default function UserActions(props) {
if (displayMode === 'list') {
return (
-
-
-
+
+
+
+
+
);
} else if (displayMode === 'edit') {
return (
-
- Back
+
+ Back
);
} else if (displayMode === 'show') {
return (
-
- Edit
+
+ Edit
);
@@ -69,49 +72,49 @@ export default function UserActions(props) {
}
return (
-
+
{renderButton()}
-
-
- {displayMode !== 'list' ? (
-
-
-
- ) : (
-
-
-
- )}
-
+
+
+
+
+
+
-
+
}>
+ onSelect={(e) => e.preventDefault()}>
+
Login as User
-
+
}
/>
- }>
- Audit Logs
-
+
+
+
+ Audit Logs
+
+
}>Inspect}
+ trigger={
+ e.preventDefault()}>
+
+ Inspect
+
+ }
/>
- Are you sure you want to delete {user.name} ({user.email}
- )?
-
+
+ Are you sure you want to delete {user.name} ({user.email})?
+
}
trigger={
- }>
+ e.preventDefault()}>
+
Delete
-
+
}
/>
-
-
-
+
+
+
);
}
diff --git a/services/web/src/screens/Users/Detail/Edit.js b/services/web/src/screens/Users/Detail/Edit.js
index 20c11ac93..b3a302909 100644
--- a/services/web/src/screens/Users/Detail/Edit.js
+++ b/services/web/src/screens/Users/Detail/Edit.js
@@ -1,5 +1,4 @@
import { useNavigate } from '@bedrockio/router';
-import { Paper } from '@mantine/core';
import React from 'react';
import { usePage } from 'stores/page';
@@ -14,7 +13,7 @@ export default function EditUser() {
return (
-
+
{
@@ -22,7 +21,7 @@ export default function EditUser() {
navigate.back();
}}
/>
-
+
);
}
diff --git a/services/web/src/screens/Users/Detail/Overview.js b/services/web/src/screens/Users/Detail/Overview.js
index fd3f8ea8c..b6d41c64f 100644
--- a/services/web/src/screens/Users/Detail/Overview.js
+++ b/services/web/src/screens/Users/Detail/Overview.js
@@ -1,22 +1,23 @@
import { Link } from '@bedrockio/router';
-import {
- Anchor,
- Badge,
- Box,
- Card,
- Divider,
- Group,
- Image,
- SimpleGrid,
- Stack,
- Text,
-} from '@mantine/core';
-
import { usePage } from 'stores/page';
+import Thumbnail from 'components/Thumbnail';
import UserImage from 'components/UserImage';
+import { Badge } from '@/components/ui/badge';
+import {
+ Card,
+ CardContent,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card';
+import {
+ DefinitionItem,
+ DefinitionList,
+} from '@/components/ui/definition-list';
+import { Separator } from '@/components/ui/separator';
+
import { useRequest } from 'utils/api';
import { formatDateTime } from 'utils/date';
import { formatRoles } from 'utils/permissions';
@@ -24,14 +25,6 @@ import { urlForUpload } from 'utils/uploads';
import Menu from './Menu';
-function LinkWrapped({ children, ...props }) {
- return (
-
- {children}
-
- );
-}
-
export default function UserOverview() {
const { user } = usePage();
@@ -49,105 +42,71 @@ export default function UserOverview() {
-
-
-
-
- User Information
-
-
-
-
-
- Name
-
- {user.name}
-
-
-
-
- Email
-
- {user.email}
-
-
-
-
- Roles
-
-
- {formatRoles(user.roles).map((label) => {
- return (
- }
- key={label.key}>
- {label.content}
-
- );
- })}
-
-
-
-
-
- Phone
-
- {user.phone || 'N / A'}
-
-
-
-
- Created At
-
- {formatDateTime(user.createdAt)}
-
-
+
+
+
+ User Information
+
+
+
+ {user.name}
+ {user.email}
+
+
+ {formatRoles(user.roles).map((label) => {
+ return (
+
+
+ {label.content}
+
+ );
+ })}
+
+
+
+ {user.phone || 'N / A'}
+
+
+ {formatDateTime(user.createdAt)}
+
+
+
-
-
-
- Shops
-
-
- {shopsRequest.data.length === 0 && (
-
- No shops yet
-
- )}
- {shopsRequest.data.map((shop) => {
- return (
-
-
-
-
-
- {shop.name}
-
-
- {shop.description}
-
-
-
-
- );
- })}
+
+
+ Shops
+
+
+ {shopsRequest.data.length === 0 && (
+ No shops yet
+ )}
+ {shopsRequest.data.map((shop, index) => {
+ return (
+
+ {index > 0 &&
}
+
+
+
+
+ {shop.name}
+
+
+ {shop.description}
+
+
+
+
+ );
+ })}
+
-
+
>
);
}
diff --git a/services/web/src/screens/Users/Form.js b/services/web/src/screens/Users/Form.js
index 579294983..017fbc2ab 100644
--- a/services/web/src/screens/Users/Form.js
+++ b/services/web/src/screens/Users/Form.js
@@ -1,11 +1,6 @@
-import {
- Button,
- Fieldset,
- LoadingOverlay,
- Switch,
- Text,
- TextInput,
-} from '@mantine/core';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
import { showSuccessNotification } from 'helpers/notifications';
@@ -15,17 +10,64 @@ import Actions from 'components/form-fields/Actions';
import PhoneField from 'components/form-fields/Phone';
import RolesField from 'components/form-fields/Roles';
import UploadsField from 'components/form-fields/Uploads';
-import { useFields } from 'hooks/forms';
import { useRequest } from 'hooks/request';
+import { Button } from '@/components/ui/button';
+import {
+ Card,
+ CardContent,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { Spinner } from '@/components/ui/spinner';
+import { Switch } from '@/components/ui/switch';
+
import { request } from 'utils/api';
+// UploadsField calls onChange as (name, value) when adding and as
+// ({ name, value }) when removing — normalise both to the value.
+function resolveUploadValue(...args) {
+ if (args.length === 2) {
+ return args[1];
+ }
+ return args[0]?.value;
+}
+
+const schema = z.object({
+ firstName: z.string().min(1, 'First Name is required'),
+ lastName: z.string().min(1, 'Last Name is required'),
+ email: z.string().min(1, 'Email is required'),
+ phone: z.string().optional().nullable(),
+ image: z.any().optional(),
+ roles: z.array(z.any()),
+ isTester: z.boolean().optional(),
+});
+
export default function UserForm(props) {
const { user } = props;
- const { fields, setField } = useFields({
- roles: [],
- ...user,
+ const form = useForm({
+ resolver: zodResolver(schema),
+ defaultValues: {
+ firstName: '',
+ lastName: '',
+ email: '',
+ phone: '',
+ image: undefined,
+ roles: [],
+ isTester: false,
+ ...user,
+ },
});
const { run, loading, error } = useRequest(async (body) => {
@@ -52,80 +94,155 @@ export default function UserForm(props) {
}
});
- function onSubmit(evt) {
- evt.preventDefault();
+ function onSubmit(fields) {
run(fields);
}
- return (
-
-
-
-
-
-
+ const submitting = loading || form.formState.isSubmitting;
-
-
-
-
-
-
-
-
-
-
-
-
- Submit
-
-
+
+ Flags
+ (
+
+
+
+
+ Tester
+
+ )}
+ />
+
+
+
+
+
+ {submitting && }
+ Submit
+
+
+
+
);
}
diff --git a/services/web/src/screens/Users/List.js b/services/web/src/screens/Users/List.js
index 3d20a3744..dfab0563f 100644
--- a/services/web/src/screens/Users/List.js
+++ b/services/web/src/screens/Users/List.js
@@ -1,21 +1,20 @@
import { Link } from '@bedrockio/router';
-import {
- Anchor,
- Badge,
- Button,
- Group,
- Loader,
- Stack,
- Table,
- Text,
-} from '@mantine/core';
-
import ErrorMessage from 'components/ErrorMessage';
import PageHeader from 'components/PageHeader';
import Search from 'components/Search';
import SearchFilters from 'components/Search/Filters';
+import { Badge } from '@/components/ui/badge';
+import { Button } from '@/components/ui/button';
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHeader,
+ TableRow,
+} from '@/components/ui/table';
+
import { request } from 'utils/api';
import { formatDateTime } from 'utils/date';
import { formatRoles } from 'utils/permissions';
@@ -47,135 +46,116 @@ export default function UserList() {
}
return (
- <>
-
- {({ items: users, reload, error, loading }) => {
- return (
-
-
-
-
- New User
-
- >
- }
- />
+
+ {({ items: users, reload, error }) => {
+ return (
+
+
+
+
+ New User
+
+ >
+ }
+ />
+
+
+
+
+
+
-
-
-
-
-
-
- {loading && }
-
+
+
+
+
+
-
-
-
-
-
+
-
+
+
+
+
+ Name
+
+ Email
+ Phone
+ Role
+
+ Created
+
+
+ Actions
+
+
+
+
+
+
+
+
+ No users found.
+
+
+
+
+ {users.map((user) => {
+ return (
+
+
+
+ {user.name}
+
+
+ {user.email}
+ {formatPhone(user.phone)}
+
+
+ {formatRoles(user.roles).map((label) => {
+ return (
+
+
+ {label.content}
+
+ );
+ })}
+
+
+ {formatDateTime(user.createdAt)}
+
+
+
+
+ );
+ })}
+
+
-
-
-
-
-
- Name
-
- Email
- Phone
- Role
-
- Created
-
-
- Actions
-
-
-
-
-
-
-
-
- No users found.
-
-
-
-
- {users.map((user) => {
- return (
-
-
-
- {user.name}
-
-
- {user.email}
- {formatPhone(user.phone)}
-
- {formatRoles(user.roles).map((label) => {
- return (
- }
- key={label.key}>
- {label.content}
-
- );
- })}
-
- {formatDateTime(user.createdAt)}
-
-
-
-
- );
- })}
-
-
-
-
-
- );
- }}
-
- >
+
+
+ );
+ }}
+
);
}
diff --git a/services/web/src/screens/Users/New.js b/services/web/src/screens/Users/New.js
index f47daf306..32fcd56db 100644
--- a/services/web/src/screens/Users/New.js
+++ b/services/web/src/screens/Users/New.js
@@ -1,15 +1,16 @@
import { Link, useNavigate } from '@bedrockio/router';
-import { Button, Stack } from '@mantine/core';
import PageHeader from 'components/PageHeader';
+import { Button } from '@/components/ui/button';
+
import Form from './Form';
export default function NewUser() {
const navigate = useNavigate();
return (
-
+
- Back
+
+ Back
}
/>
@@ -29,6 +30,6 @@ export default function NewUser() {
navigate(`/users/${user.id}`);
}}
/>
-
+
);
}
diff --git a/services/web/src/stores/page.js b/services/web/src/stores/page.js
index c55d38fc4..5f54aa881 100644
--- a/services/web/src/stores/page.js
+++ b/services/web/src/stores/page.js
@@ -1,5 +1,6 @@
import { useLocation, useParams } from '@bedrockio/router';
-import { Loader } from '@mantine/core';
+
+import { Spinner } from '@/components/ui/spinner';
import {
createContext,
useCallback,
@@ -78,7 +79,13 @@ function useLoader(names, params, fn) {
(props) => {
const { fallback, notFound } = props;
if (loading) {
- return fallback || ;
+ return (
+ fallback || (
+
+
+
+ )
+ );
} else if (error) {
if (error.status === 404 && notFound) {
return notFound;
diff --git a/services/web/src/styles/globals.css b/services/web/src/styles/globals.css
new file mode 100644
index 000000000..525edde42
--- /dev/null
+++ b/services/web/src/styles/globals.css
@@ -0,0 +1,174 @@
+/*
+ * shadcn/ui + Tailwind v4 design tokens.
+ *
+ * NOTE: Tailwind's preflight (global base reset) is intentionally NOT imported
+ * while Mantine is still mounted — it would reset Mantine's base element styles
+ * and break unconverted screens (see change-ui-framework-to-shadcn.html §6.3).
+ * We import theme + utilities + the shadcn component layer only. Once Mantine is
+ * removed in the teardown phase, switch the two imports below back to a single
+ * `@import "tailwindcss";` to re-enable preflight.
+ */
+/* Mantine is gone — full Tailwind (incl. preflight) is back in its normal layers. */
+@import 'tailwindcss';
+@import 'tw-animate-css';
+
+@custom-variant dark (&:is(.dark *));
+
+/*
+ * Theme = the literal shadcn/ui default (neutral base, oklch).
+ * See restyle-to-shadcn-default.html.
+ *
+ * ┌──────────────────────────────────────────────────────────────────────────┐
+ * │ TO BRAND THE APP — change the primary colour │
+ * │ │
+ * │ The default primary is the shadcn neutral (near-black in light, near- │
+ * │ white in dark). To give the app a brand colour, set --primary to that │
+ * │ colour and --primary-foreground to a contrasting text colour, in BOTH │
+ * │ :root (light) and .dark below. They are tagged `BRAND PRIMARY` inline. │
+ * │ │
+ * │ Example — Bedrock green: │
+ * │ :root { --primary: oklch(0.55 0.12 152); --primary-foreground: oklch(0.985 0 0); }
+ * │ .dark { --primary: oklch(0.62 0.13 152); --primary-foreground: oklch(0.205 0 0); }
+ * │ │
+ * │ Optionally also brand --ring (focus) and --sidebar-primary to match. │
+ * │ Nothing else needs to change — every primary button, focus ring and │
+ * │ active state reads from these tokens. │
+ * └──────────────────────────────────────────────────────────────────────────┘
+ */
+:root {
+ --radius: 0.625rem;
+
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.145 0 0);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.145 0 0);
+
+ --primary: oklch(0.205 0 0); /* BRAND PRIMARY (light) — see header */
+ --primary-foreground: oklch(0.985 0 0);
+ --secondary: oklch(0.97 0 0);
+ --secondary-foreground: oklch(0.205 0 0);
+ --muted: oklch(0.97 0 0);
+ --muted-foreground: oklch(0.556 0 0);
+ --accent: oklch(0.97 0 0);
+ --accent-foreground: oklch(0.205 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
+ --destructive-foreground: oklch(0.985 0 0);
+
+ --border: oklch(0.922 0 0);
+ --input: oklch(0.922 0 0);
+ --ring: oklch(0.708 0 0);
+
+ /* semantic (Alert info/success/warning) — meaning-only colours (§5.6) */
+ --info: oklch(0.6 0.118 248);
+ --success: oklch(0.6 0.13 150);
+ --warning: oklch(0.68 0.15 65);
+
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.145 0 0);
+ --sidebar-primary: oklch(0.205 0 0);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.97 0 0);
+ --sidebar-accent-foreground: oklch(0.205 0 0);
+ --sidebar-border: oklch(0.922 0 0);
+ --sidebar-ring: oklch(0.708 0 0);
+}
+
+.dark {
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.205 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.205 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+
+ --primary: oklch(0.922 0 0); /* BRAND PRIMARY (dark) — see header */
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.269 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.704 0.191 22.216);
+ --destructive-foreground: oklch(0.985 0 0);
+
+ --border: oklch(1 0 0 / 10%);
+ --input: oklch(1 0 0 / 15%);
+ --ring: oklch(0.556 0 0);
+
+ --info: oklch(0.68 0.13 248);
+ --success: oklch(0.68 0.14 150);
+ --warning: oklch(0.75 0.15 65);
+
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.922 0 0);
+ --sidebar-primary-foreground: oklch(0.205 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(1 0 0 / 10%);
+ --sidebar-ring: oklch(0.556 0 0);
+}
+
+@theme inline {
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-destructive-foreground: var(--destructive-foreground);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+
+ --color-info: var(--info);
+ --color-success: var(--success);
+ --color-warning: var(--warning);
+
+ --color-sidebar: var(--sidebar);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-ring: var(--sidebar-ring);
+
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+
+ --font-sans: 'Geist Variable', system-ui, -apple-system, BlinkMacSystemFont,
+ 'Segoe UI', Helvetica, Arial, sans-serif;
+ --font-mono: 'Geist Mono Variable', ui-monospace, SFMono-Regular, Menlo,
+ Consolas, monospace;
+}
+
+/*
+ * Canonical shadcn base layer. Without this:
+ * - bare `border`/`border-b` (table rows, Cards, definition lists) default to
+ * currentColor (dark) instead of the subtle `--border` token → heavy lines;
+ * - elements with no explicit text colour (outline/ghost buttons) fall back to
+ * UA black, which is invisible/wrong in dark mode.
+ */
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
diff --git a/services/web/src/theme.js b/services/web/src/theme.js
deleted file mode 100644
index 996733096..000000000
--- a/services/web/src/theme.js
+++ /dev/null
@@ -1,150 +0,0 @@
-import { Button, DEFAULT_THEME, Tabs, createTheme } from '@mantine/core';
-import '@mantine/core/styles.css';
-import '@mantine/dates/styles.css';
-import '@mantine/notifications/styles.css';
-
-import './theme.less';
-
-export const theme = createTheme({
- primaryShade: 9,
- autoContrast: true,
-
- luminanceThreshold: 0.3,
- primaryColor: 'green',
- fontFamily:
- 'system-ui,-apple-system,BlinkMacSystemFont,Helvetica,"Segoe UI",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',
-
- colors: {
- brown: [
- '#fcfbfa',
- '#f4f0ea',
- '#e8e1d6',
- '#d8cfc0',
- '#c8bda9',
- '#b8ab93',
- '#a89a7d',
- '#99896a',
- '#8a7857',
- '#7b673f',
- '#6c5629',
- ],
- green: [
- '#ebfbee',
- '#d3f9d8',
- '#b2f2bb',
- '#8ce99a',
- '#69db7c',
- '#51cf66',
- '#40c057',
- '#37b24d',
- '#2f9e44',
- '#2b8a3e',
- '#054011',
- ],
-
- error: DEFAULT_THEME.colors.red,
- info: DEFAULT_THEME.colors.blue,
- success: DEFAULT_THEME.colors.green,
- warning: DEFAULT_THEME.colors.orange,
- },
- components: {
- Button: Button.extend({
- defaultProps: {
- size: 'sm',
- },
- }),
- AppShell: {
- styles: {
- navbar: {
- backgroundColor: `light-dark(var(--mantine-color-brown-1), transparent)`,
- },
- header: {
- backgroundColor: `light-dark(var(--mantine-color-brown-1), transparent)`,
- },
- main: {
- backgroundColor: `light-dark(var(--mantine-color-brown-0), transparent)`,
- },
- },
- },
- Anchor: {
- styles: {
- root: {
- color: 'var(--mantine-color-text)',
- },
- },
- },
- Fieldset: {
- styles: {
- legend: {
- fontSize: 'var(--mantine-font-size-sm)',
- color: 'var(--mantine-color-gray-text)',
- fontWeight: 'bold',
- },
- },
- },
- Tabs: Tabs.extend({
- vars: () => ({
- tab: {
- '--tab-hover-color': 'transparent',
- },
- }),
- styles: {
- tab: {
- padding: 'var(--mantine-spacing-xs) 0rem',
- marginRight: 'var(--mantine-spacing-md)',
- },
- },
- }),
- Breadcrumbs: {
- styles: {
- root: {
- fontSize: 'var(--mantine-font-size-xs)',
- },
- breadcrumb: {
- fontSize: 'var(--mantine-font-size-xs)',
- fontWeight: '500',
- textTransform: 'uppercase',
- },
- },
- },
- Table: {
- styles: {
- thead: {
- backgroundColor: `light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-7))`,
- },
- // sadly needed for when stickyHeader is used
- th: {
- backgroundColor: 'transparent',
- },
- },
- },
- Modal: {
- styles: {
- title: {
- fontWeight: 'bold',
- },
- },
- },
- Drawer: {
- styles: {
- title: {
- fontWeight: 'bold',
- },
- },
- },
- Select: {
- styles: {
- dropdown: {
- borderColor: 'var(--mantine-color-gray-5)',
- },
- },
- },
- MultiSelect: {
- styles: {
- dropdown: {
- borderColor: 'var(--mantine-color-gray-5)',
- },
- },
- },
- },
-});
diff --git a/services/web/src/theme.less b/services/web/src/theme.less
deleted file mode 100644
index 54c038224..000000000
--- a/services/web/src/theme.less
+++ /dev/null
@@ -1,110 +0,0 @@
-:root {
- --ai-size-md: calc(5rem * var(--mantine-scale));
- --popover-shadow: 0px 1px 2px rgb(0 0 0 / 30%);
- --switch-cursor: pointer;
-}
-
-.mantine-NavLink-root {
- font-weight: 500;
- padding-left: var(--mantine-spacing-lg);
-
- &:hover {
- background: var(--mantine-primary-color-light-hover);
- }
-
- &[data-active] {
- color: light-dark(
- var(--mantine-primary-color-10),
- var(--mantine-primary-color-4)
- );
- }
-
- &[data-level='2'] {
- padding-inline-start: calc(var(--mantine-spacing-lg) + 1em);
- }
-
- &[data-level='3'] {
- padding-inline-start: calc(var(--mantine-spacing-lg) + 2em);
- }
-}
-
-.mantine-NavLink-children {
- padding-inline-start: 0;
-}
-
-.mantine-AppShell-main {
- display: flex;
- flex-flow: column;
-}
-
-.mantine-ActionIcon-root {
- padding: 6px;
- --ai-size-md: 32px;
-}
-
-.mantine-Typography-root {
- :where(pre) {
- font-size: inherit;
- }
-}
-
-.mantine-Divider-root {
- --divider-color: rgb(0 0 0 / 10%);
-}
-
-.mantine-Textarea-input {
- padding: 0.5em;
-}
-
-.mantine-Alert-root:not(:last-child) {
- margin-bottom: var(--mantine-spacing-sm);
-}
-
-.mantine-Select-dropdown {
- box-shadow: 0px 1px 3px rgb(0 0 0 / 25%);
- border: 1px solid #ddd;
-}
-
-.mantine-Select-empty,
-.mantine-MultiSelect-empty {
- text-align: left;
-}
-
-.mantine-Checkbox-label {
- cursor: pointer;
-}
-
-form {
- display: flex;
- flex-flow: column;
- gap: 1em;
-}
-
-:any-link[data-styled] {
- color: inherit;
- text-decoration: none;
-}
-
-.mantine-Table-td ul {
- margin: 0;
- list-style-type: '→';
- padding-left: 1em;
-
- li {
- padding-left: 0.5em;
-
- &::marker {
- color: rgb(0 0 0 / 30%);
- }
- }
-}
-
-.mantine-Fieldset-root {
- display: flex;
- flex-flow: column;
- gap: 0.75em;
-}
-
-.mantine-Switch-label {
- cursor: pointer;
-}
diff --git a/services/web/src/utils/notify.js b/services/web/src/utils/notify.js
new file mode 100644
index 000000000..baa52747d
--- /dev/null
+++ b/services/web/src/utils/notify.js
@@ -0,0 +1,36 @@
+import { toast } from 'sonner';
+
+/**
+ * Thin wrapper around sonner's `toast`. Accepts a `{ title, message, color }`
+ * shape so call sites can fire a notification with a single `notify(...)` call,
+ * mapping a colour keyword to the matching toast variant.
+ *
+ * notify({ title, message, color }) // color: red|green|orange|blue|…
+ */
+export function notify({ title, message, color } = {}) {
+ const heading = title || message;
+ const description = title && message ? message : undefined;
+ const opts = description ? { description } : undefined;
+
+ switch (color) {
+ case 'red':
+ case 'error':
+ return toast.error(heading, opts);
+ case 'green':
+ case 'teal':
+ case 'success':
+ return toast.success(heading, opts);
+ case 'yellow':
+ case 'orange':
+ case 'warning':
+ return toast.warning(heading, opts);
+ case 'blue':
+ case 'info':
+ return toast.info(heading, opts);
+ default:
+ return toast(heading, opts);
+ }
+}
+
+export const notifySuccess = (params) => notify({ ...params, color: 'green' });
+export const notifyError = (params) => notify({ ...params, color: 'red' });
diff --git a/services/web/vite.config.js b/services/web/vite.config.js
index 7c549d59b..98e5ea0b4 100644
--- a/services/web/vite.config.js
+++ b/services/web/vite.config.js
@@ -4,6 +4,7 @@ import path from 'path';
import config from '@bedrockio/config';
import mdx from '@mdx-js/rollup';
+import tailwindcss from '@tailwindcss/vite';
import react from '@vitejs/plugin-react';
import { omitBy, template } from 'lodash-es';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
@@ -31,6 +32,7 @@ export default defineConfig({
rehypePlugins: [rehypeSlug, rehypeAutolinkHeadings],
}),
react(),
+ tailwindcss(),
env(),
partials(),
],
@@ -39,6 +41,7 @@ export default defineConfig({
resolve: {
alias: {
lodash: 'lodash-es',
+ '@': path.resolve(__dirname, './src'),
helpers: path.resolve(__dirname, './src/helpers'),
screens: path.resolve(__dirname, './src/screens'),
stores: path.resolve(__dirname, './src/stores'),
diff --git a/services/web/yarn.lock b/services/web/yarn.lock
index 7b138a927..f37798d76 100644
--- a/services/web/yarn.lock
+++ b/services/web/yarn.lock
@@ -138,7 +138,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.27.1"
-"@babel/runtime@^7.12.5", "@babel/runtime@^7.20.13", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
+"@babel/runtime@^7.12.5":
version "7.28.3"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.3.tgz#75c5034b55ba868121668be5d5bb31cc64e6e61a"
integrity sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==
@@ -236,6 +236,28 @@
resolved "https://registry.yarnpkg.com/@date-fns/tz/-/tz-1.4.1.tgz#2d905f282304630e07bef6d02d2e7dbf3f0cc4e4"
integrity sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==
+"@emnapi/core@^1.10.0":
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.10.0.tgz#380ccc8f2412ea22d1d972df7f8ee23a3b9c7467"
+ integrity sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==
+ dependencies:
+ "@emnapi/wasi-threads" "1.2.1"
+ tslib "^2.4.0"
+
+"@emnapi/runtime@^1.10.0":
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.10.0.tgz#4b260c0d3534204e98c6110b8db1a987d26ec87c"
+ integrity sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==
+ dependencies:
+ tslib "^2.4.0"
+
+"@emnapi/wasi-threads@1.2.1", "@emnapi/wasi-threads@^1.2.1":
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz#28fed21a1ba1ce797c44a070abc94d42f3ae8548"
+ integrity sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==
+ dependencies:
+ tslib "^2.4.0"
+
"@esbuild/aix-ppc64@0.25.9":
version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz#bef96351f16520055c947aba28802eede3c9e9a9"
@@ -458,41 +480,42 @@
"@eslint/core" "^0.16.0"
levn "^0.4.1"
-"@floating-ui/core@^1.7.3":
- version "1.7.3"
- resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.7.3.tgz#462d722f001e23e46d86fd2bd0d21b7693ccb8b7"
- integrity sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==
+"@floating-ui/core@^1.7.5":
+ version "1.7.5"
+ resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.7.5.tgz#d4af157a03330af5a60e69da7a4692507ada0622"
+ integrity sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==
dependencies:
- "@floating-ui/utils" "^0.2.10"
+ "@floating-ui/utils" "^0.2.11"
-"@floating-ui/dom@^1.7.4":
- version "1.7.4"
- resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.4.tgz#ee667549998745c9c3e3e84683b909c31d6c9a77"
- integrity sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==
+"@floating-ui/dom@^1.7.6":
+ version "1.7.6"
+ resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.6.tgz#f915bba5abbb177e1f227cacee1b4d0634b187bf"
+ integrity sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==
dependencies:
- "@floating-ui/core" "^1.7.3"
- "@floating-ui/utils" "^0.2.10"
+ "@floating-ui/core" "^1.7.5"
+ "@floating-ui/utils" "^0.2.11"
-"@floating-ui/react-dom@^2.1.6":
- version "2.1.6"
- resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.6.tgz#189f681043c1400561f62972f461b93f01bf2231"
- integrity sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==
+"@floating-ui/react-dom@^2.0.0":
+ version "2.1.8"
+ resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.8.tgz#5fb5a20d10aafb9505f38c24f38d00c8e1598893"
+ integrity sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==
dependencies:
- "@floating-ui/dom" "^1.7.4"
+ "@floating-ui/dom" "^1.7.6"
-"@floating-ui/react@^0.27.16":
- version "0.27.16"
- resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.27.16.tgz#6e485b5270b7a3296fdc4d0faf2ac9abf955a2f7"
- integrity sha512-9O8N4SeG2z++TSM8QA/KTeKFBVCNEz/AGS7gWPJf6KFRzmRWixFRnCnkPHRDwSVZW6QPDO6uT0P2SpWNKCc9/g==
- dependencies:
- "@floating-ui/react-dom" "^2.1.6"
- "@floating-ui/utils" "^0.2.10"
- tabbable "^6.0.0"
+"@floating-ui/utils@^0.2.11":
+ version "0.2.11"
+ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.11.tgz#a269e055e40e2f45873bae9d1a2fdccbd314ea3f"
+ integrity sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==
-"@floating-ui/utils@^0.2.10":
- version "0.2.10"
- resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.10.tgz#a2a1e3812d14525f725d011a73eceb41fef5bc1c"
- integrity sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==
+"@fontsource-variable/geist-mono@^5.2.8":
+ version "5.2.8"
+ resolved "https://registry.yarnpkg.com/@fontsource-variable/geist-mono/-/geist-mono-5.2.8.tgz#b71fbb9d8332dde974adeba3dbe5180c4ce9e9aa"
+ integrity sha512-KI5bj+hkkRiHttYHmccotUZ80ZuZyai+RwI1d7UId0clkx/jXxlo8qYK8j54WzmpBjtMoEMPyllV7faDcj+6RA==
+
+"@fontsource-variable/geist@^5.2.9":
+ version "5.2.9"
+ resolved "https://registry.yarnpkg.com/@fontsource-variable/geist/-/geist-5.2.9.tgz#261356515cb97e51bee31991259776b7435f0bd8"
+ integrity sha512-TP+QSBG3wxKGPE33CbMy/L0Nu3qvJ6Fy81Yc4LnQ95xH+i+cfEp8fyU8/kfV14YwszxIFPhnoMTbjL71waVpyQ==
"@google-cloud/opentelemetry-cloud-trace-exporter@^2.4.1":
version "2.4.1"
@@ -530,6 +553,13 @@
protobufjs "^7.2.5"
yargs "^17.7.2"
+"@hookform/resolvers@^5.4.0":
+ version "5.4.0"
+ resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-5.4.0.tgz#89ff709a08576766fbef849e5ec60e549a888006"
+ integrity sha512-EIsqr/t/qbinPIhGjMdtvutIN1Kk4uwbROE9/UQ93CAVGR7GkA7Y92+fX80OzXi/OB67jVFYwKGO1WzkxmkFZw==
+ dependencies:
+ "@standard-schema/utils" "^0.3.0"
+
"@humanfs/core@^0.19.1":
version "0.19.1"
resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77"
@@ -578,12 +608,20 @@
"@jridgewell/sourcemap-codec" "^1.5.0"
"@jridgewell/trace-mapping" "^0.3.24"
+"@jridgewell/remapping@^2.3.5":
+ version "2.3.5"
+ resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1"
+ integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.5"
+ "@jridgewell/trace-mapping" "^0.3.24"
+
"@jridgewell/resolve-uri@^3.1.0":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
-"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0":
+"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.5.5":
version "1.5.5"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba"
integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
@@ -601,51 +639,6 @@
resolved "https://registry.yarnpkg.com/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz#9299f82874bab9e4c7f9c48d865becbfe8d6907c"
integrity sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==
-"@mantine/core@^8.3.10":
- version "8.3.10"
- resolved "https://registry.yarnpkg.com/@mantine/core/-/core-8.3.10.tgz#f9c2f9ae6dd68836a90f0899f4aed10ac4e31703"
- integrity sha512-aKQFETN14v6GtM07b/G5yJneMM1yrgf9mNrTah6GVy5DvQM0AeutITT7toHqh5gxxwzdg/DoY+HQsv5zhqnc5g==
- dependencies:
- "@floating-ui/react" "^0.27.16"
- clsx "^2.1.1"
- react-number-format "^5.4.4"
- react-remove-scroll "^2.7.1"
- react-textarea-autosize "8.5.9"
- type-fest "^4.41.0"
-
-"@mantine/dates@^8.3.10":
- version "8.3.10"
- resolved "https://registry.yarnpkg.com/@mantine/dates/-/dates-8.3.10.tgz#c79490a5a8cec4cb0c35bc9568da9bcb705e11e7"
- integrity sha512-P1uZ+alYGp7fsmkfd+7Fur4AGrqT0X6BWLiVTomzrbyykA+m4TSwPyQjKfsDc7XRqaqx992br/U65T82zy+qGQ==
- dependencies:
- clsx "^2.1.1"
-
-"@mantine/form@^8.3.10":
- version "8.3.10"
- resolved "https://registry.yarnpkg.com/@mantine/form/-/form-8.3.10.tgz#2b56e53457bd71c39338d18c42c6aae68ce48dd6"
- integrity sha512-TuBmCUIH0qHUig+y9My3bLL9CRoW4g9bijIF6743gqVh0o/daSwplc2TTVMj6sl+F1MR+SJiHtAC8FoR7fdhNw==
- dependencies:
- fast-deep-equal "^3.1.3"
- klona "^2.0.6"
-
-"@mantine/hooks@^8.3.10":
- version "8.3.10"
- resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-8.3.10.tgz#610e306e7d5609d4994b9ffba57a9a1bfc96d03a"
- integrity sha512-bv+yYHl+keTIvakiDzVJMIjW+o8/Px0G3EdpCMFG+U2ux6SwQqluqoq+/kqrTtT6RaLvQ0fMxjpIULF2cu/xAg==
-
-"@mantine/notifications@^8.3.10":
- version "8.3.10"
- resolved "https://registry.yarnpkg.com/@mantine/notifications/-/notifications-8.3.10.tgz#a2aeb401d90cca499ee10198d607fbfb3373cefd"
- integrity sha512-0aVpRCyn9u0wuryBnFu1jOwBYw6xGeaNNtTcTUnSvkL6NAypfPon6JG7Wsekf3IuWSTLBjhYaFEIEd4nh7VDpg==
- dependencies:
- "@mantine/store" "8.3.10"
- react-transition-group "4.4.5"
-
-"@mantine/store@8.3.10":
- version "8.3.10"
- resolved "https://registry.yarnpkg.com/@mantine/store/-/store-8.3.10.tgz#86d5cc35c2f16c3855840ff153a24a2787706f45"
- integrity sha512-38t1UivcucZo9hQq27F/eqR5GvovNs4NHEz6DchOuZzV5IJWqO8+T07ivb8wct47ovYe42rPfLcaOdnIEvMsJA==
-
"@mdx-js/mdx@^3.0.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-3.1.0.tgz#10235cab8ad7d356c262e8c21c68df5850a97dc3"
@@ -686,6 +679,13 @@
source-map "^0.7.0"
vfile "^6.0.0"
+"@napi-rs/wasm-runtime@^1.1.4":
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz#a46bbfedc29751b7170c5d23bc1d8ee8c7e3c1e1"
+ integrity sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==
+ dependencies:
+ "@tybys/wasm-util" "^0.10.1"
+
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@@ -959,6 +959,419 @@
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
+"@radix-ui/number@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.1.1.tgz#7b2c9225fbf1b126539551f5985769d0048d9090"
+ integrity sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==
+
+"@radix-ui/primitive@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.3.tgz#e2dbc13bdc5e4168f4334f75832d7bdd3e2de5ba"
+ integrity sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==
+
+"@radix-ui/react-arrow@1.1.7":
+ version "1.1.7"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz#e14a2657c81d961598c5e72b73dd6098acc04f09"
+ integrity sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==
+ dependencies:
+ "@radix-ui/react-primitive" "2.1.3"
+
+"@radix-ui/react-avatar@^1.1.11":
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-avatar/-/react-avatar-1.1.11.tgz#3e24b70d636a12e2806abb2b4ce4b15df395f9c9"
+ integrity sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==
+ dependencies:
+ "@radix-ui/react-context" "1.1.3"
+ "@radix-ui/react-primitive" "2.1.4"
+ "@radix-ui/react-use-callback-ref" "1.1.1"
+ "@radix-ui/react-use-is-hydrated" "0.1.0"
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+
+"@radix-ui/react-checkbox@^1.3.3":
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz#db45ca8a6d5c056a92f74edbb564acee05318b79"
+ integrity sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-presence" "1.1.5"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+ "@radix-ui/react-use-previous" "1.1.1"
+ "@radix-ui/react-use-size" "1.1.1"
+
+"@radix-ui/react-collection@1.1.7":
+ version "1.1.7"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.1.7.tgz#d05c25ca9ac4695cc19ba91f42f686e3ea2d9aec"
+ integrity sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==
+ dependencies:
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-slot" "1.2.3"
+
+"@radix-ui/react-compose-refs@1.1.2", "@radix-ui/react-compose-refs@^1.1.1":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz#a2c4c47af6337048ee78ff6dc0d090b390d2bb30"
+ integrity sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==
+
+"@radix-ui/react-context@1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.2.tgz#61628ef269a433382c364f6f1e3788a6dc213a36"
+ integrity sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==
+
+"@radix-ui/react-context@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.3.tgz#81286f643b310d040eaac13b18e223130861d839"
+ integrity sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==
+
+"@radix-ui/react-dialog@^1.1.15", "@radix-ui/react-dialog@^1.1.6":
+ version "1.1.15"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz#1de3d7a7e9a17a9874d29c07f5940a18a119b632"
+ integrity sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-dismissable-layer" "1.1.11"
+ "@radix-ui/react-focus-guards" "1.1.3"
+ "@radix-ui/react-focus-scope" "1.1.7"
+ "@radix-ui/react-id" "1.1.1"
+ "@radix-ui/react-portal" "1.1.9"
+ "@radix-ui/react-presence" "1.1.5"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-slot" "1.2.3"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+ aria-hidden "^1.2.4"
+ react-remove-scroll "^2.6.3"
+
+"@radix-ui/react-direction@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.1.1.tgz#39e5a5769e676c753204b792fbe6cf508e550a14"
+ integrity sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==
+
+"@radix-ui/react-dismissable-layer@1.1.11":
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz#e33ab6f6bdaa00f8f7327c408d9f631376b88b37"
+ integrity sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-callback-ref" "1.1.1"
+ "@radix-ui/react-use-escape-keydown" "1.1.1"
+
+"@radix-ui/react-dropdown-menu@^2.1.16":
+ version "2.1.16"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz#5ee045c62bad8122347981c479d92b1ff24c7254"
+ integrity sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-id" "1.1.1"
+ "@radix-ui/react-menu" "2.1.16"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+
+"@radix-ui/react-focus-guards@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz#2a5669e464ad5fde9f86d22f7fdc17781a4dfa7f"
+ integrity sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==
+
+"@radix-ui/react-focus-scope@1.1.7":
+ version "1.1.7"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz#dfe76fc103537d80bf42723a183773fd07bfb58d"
+ integrity sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==
+ dependencies:
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-callback-ref" "1.1.1"
+
+"@radix-ui/react-id@1.1.1", "@radix-ui/react-id@^1.1.0":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.1.1.tgz#1404002e79a03fe062b7e3864aa01e24bd1471f7"
+ integrity sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==
+ dependencies:
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+
+"@radix-ui/react-label@^2.1.8":
+ version "2.1.8"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-label/-/react-label-2.1.8.tgz#d93b7c063ef2ea034df143a2464bfc0548e4b7e5"
+ integrity sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==
+ dependencies:
+ "@radix-ui/react-primitive" "2.1.4"
+
+"@radix-ui/react-menu@2.1.16":
+ version "2.1.16"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-2.1.16.tgz#528a5a973c3a7413d3d49eb9ccd229aa52402911"
+ integrity sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-collection" "1.1.7"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-direction" "1.1.1"
+ "@radix-ui/react-dismissable-layer" "1.1.11"
+ "@radix-ui/react-focus-guards" "1.1.3"
+ "@radix-ui/react-focus-scope" "1.1.7"
+ "@radix-ui/react-id" "1.1.1"
+ "@radix-ui/react-popper" "1.2.8"
+ "@radix-ui/react-portal" "1.1.9"
+ "@radix-ui/react-presence" "1.1.5"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-roving-focus" "1.1.11"
+ "@radix-ui/react-slot" "1.2.3"
+ "@radix-ui/react-use-callback-ref" "1.1.1"
+ aria-hidden "^1.2.4"
+ react-remove-scroll "^2.6.3"
+
+"@radix-ui/react-popover@^1.1.15":
+ version "1.1.15"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-popover/-/react-popover-1.1.15.tgz#9c852f93990a687ebdc949b2c3de1f37cdc4c5d5"
+ integrity sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-dismissable-layer" "1.1.11"
+ "@radix-ui/react-focus-guards" "1.1.3"
+ "@radix-ui/react-focus-scope" "1.1.7"
+ "@radix-ui/react-id" "1.1.1"
+ "@radix-ui/react-popper" "1.2.8"
+ "@radix-ui/react-portal" "1.1.9"
+ "@radix-ui/react-presence" "1.1.5"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-slot" "1.2.3"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+ aria-hidden "^1.2.4"
+ react-remove-scroll "^2.6.3"
+
+"@radix-ui/react-popper@1.2.8":
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.2.8.tgz#a79f39cdd2b09ab9fb50bf95250918422c4d9602"
+ integrity sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==
+ dependencies:
+ "@floating-ui/react-dom" "^2.0.0"
+ "@radix-ui/react-arrow" "1.1.7"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-callback-ref" "1.1.1"
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+ "@radix-ui/react-use-rect" "1.1.1"
+ "@radix-ui/react-use-size" "1.1.1"
+ "@radix-ui/rect" "1.1.1"
+
+"@radix-ui/react-portal@1.1.9":
+ version "1.1.9"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.9.tgz#14c3649fe48ec474ac51ed9f2b9f5da4d91c4472"
+ integrity sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==
+ dependencies:
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+
+"@radix-ui/react-presence@1.1.5":
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.5.tgz#5d8f28ac316c32f078afce2996839250c10693db"
+ integrity sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==
+ dependencies:
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+
+"@radix-ui/react-primitive@2.1.3":
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz#db9b8bcff49e01be510ad79893fb0e4cda50f1bc"
+ integrity sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==
+ dependencies:
+ "@radix-ui/react-slot" "1.2.3"
+
+"@radix-ui/react-primitive@2.1.4", "@radix-ui/react-primitive@^2.0.2":
+ version "2.1.4"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz#2626ea309ebd63bf5767d3e7fc4081f81b993df0"
+ integrity sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==
+ dependencies:
+ "@radix-ui/react-slot" "1.2.4"
+
+"@radix-ui/react-roving-focus@1.1.11":
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz#ef54384b7361afc6480dcf9907ef2fedb5080fd9"
+ integrity sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-collection" "1.1.7"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-direction" "1.1.1"
+ "@radix-ui/react-id" "1.1.1"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-callback-ref" "1.1.1"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+
+"@radix-ui/react-select@^2.2.6":
+ version "2.2.6"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-select/-/react-select-2.2.6.tgz#022cf8dab16bf05d0d1b4df9e53e4bea1b744fd9"
+ integrity sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==
+ dependencies:
+ "@radix-ui/number" "1.1.1"
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-collection" "1.1.7"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-direction" "1.1.1"
+ "@radix-ui/react-dismissable-layer" "1.1.11"
+ "@radix-ui/react-focus-guards" "1.1.3"
+ "@radix-ui/react-focus-scope" "1.1.7"
+ "@radix-ui/react-id" "1.1.1"
+ "@radix-ui/react-popper" "1.2.8"
+ "@radix-ui/react-portal" "1.1.9"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-slot" "1.2.3"
+ "@radix-ui/react-use-callback-ref" "1.1.1"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+ "@radix-ui/react-use-previous" "1.1.1"
+ "@radix-ui/react-visually-hidden" "1.2.3"
+ aria-hidden "^1.2.4"
+ react-remove-scroll "^2.6.3"
+
+"@radix-ui/react-separator@^1.1.8":
+ version "1.1.8"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.1.8.tgz#24f871fbf9630af316d0c14cbc7519a6e33aa11e"
+ integrity sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==
+ dependencies:
+ "@radix-ui/react-primitive" "2.1.4"
+
+"@radix-ui/react-slot@1.2.3":
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz#502d6e354fc847d4169c3bc5f189de777f68cfe1"
+ integrity sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==
+ dependencies:
+ "@radix-ui/react-compose-refs" "1.1.2"
+
+"@radix-ui/react-slot@1.2.4", "@radix-ui/react-slot@^1.2.4":
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.2.4.tgz#63c0ba05fdf90cc49076b94029c852d7bac1fb83"
+ integrity sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==
+ dependencies:
+ "@radix-ui/react-compose-refs" "1.1.2"
+
+"@radix-ui/react-switch@^1.2.6":
+ version "1.2.6"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-switch/-/react-switch-1.2.6.tgz#ff79acb831f0d5ea9216cfcc5b939912571358e3"
+ integrity sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+ "@radix-ui/react-use-previous" "1.1.1"
+ "@radix-ui/react-use-size" "1.1.1"
+
+"@radix-ui/react-tabs@^1.1.13":
+ version "1.1.13"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz#3537ce379d7e7ff4eeb6b67a0973e139c2ac1f15"
+ integrity sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-direction" "1.1.1"
+ "@radix-ui/react-id" "1.1.1"
+ "@radix-ui/react-presence" "1.1.5"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-roving-focus" "1.1.11"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+
+"@radix-ui/react-tooltip@^1.2.8":
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz#3f50267e25bccfc9e20bb3036bfd9ab4c2c30c2c"
+ integrity sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==
+ dependencies:
+ "@radix-ui/primitive" "1.1.3"
+ "@radix-ui/react-compose-refs" "1.1.2"
+ "@radix-ui/react-context" "1.1.2"
+ "@radix-ui/react-dismissable-layer" "1.1.11"
+ "@radix-ui/react-id" "1.1.1"
+ "@radix-ui/react-popper" "1.2.8"
+ "@radix-ui/react-portal" "1.1.9"
+ "@radix-ui/react-presence" "1.1.5"
+ "@radix-ui/react-primitive" "2.1.3"
+ "@radix-ui/react-slot" "1.2.3"
+ "@radix-ui/react-use-controllable-state" "1.2.2"
+ "@radix-ui/react-visually-hidden" "1.2.3"
+
+"@radix-ui/react-use-callback-ref@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz#62a4dba8b3255fdc5cc7787faeac1c6e4cc58d40"
+ integrity sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==
+
+"@radix-ui/react-use-controllable-state@1.2.2":
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz#905793405de57d61a439f4afebbb17d0645f3190"
+ integrity sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==
+ dependencies:
+ "@radix-ui/react-use-effect-event" "0.0.2"
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+
+"@radix-ui/react-use-effect-event@0.0.2":
+ version "0.0.2"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz#090cf30d00a4c7632a15548512e9152217593907"
+ integrity sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==
+ dependencies:
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+
+"@radix-ui/react-use-escape-keydown@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz#b3fed9bbea366a118f40427ac40500aa1423cc29"
+ integrity sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==
+ dependencies:
+ "@radix-ui/react-use-callback-ref" "1.1.1"
+
+"@radix-ui/react-use-is-hydrated@0.1.0":
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz#544da73369517036c77659d7cdd019dc0f5ff9a0"
+ integrity sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==
+ dependencies:
+ use-sync-external-store "^1.5.0"
+
+"@radix-ui/react-use-layout-effect@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz#0c4230a9eed49d4589c967e2d9c0d9d60a23971e"
+ integrity sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==
+
+"@radix-ui/react-use-previous@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz#1a1ad5568973d24051ed0af687766f6c7cb9b5b5"
+ integrity sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==
+
+"@radix-ui/react-use-rect@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz#01443ca8ed071d33023c1113e5173b5ed8769152"
+ integrity sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==
+ dependencies:
+ "@radix-ui/rect" "1.1.1"
+
+"@radix-ui/react-use-size@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz#6de276ffbc389a537ffe4316f5b0f24129405b37"
+ integrity sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==
+ dependencies:
+ "@radix-ui/react-use-layout-effect" "1.1.1"
+
+"@radix-ui/react-visually-hidden@1.2.3":
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz#a8c38c8607735dc9f05c32f87ab0f9c2b109efbf"
+ integrity sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==
+ dependencies:
+ "@radix-ui/react-primitive" "2.1.3"
+
+"@radix-ui/rect@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.1.tgz#78244efe12930c56fd255d7923865857c41ac8cb"
+ integrity sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==
+
"@rolldown/pluginutils@1.0.0-beta.27":
version "1.0.0-beta.27"
resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz#47d2bf4cef6d470b22f5831b420f8964e0bf755f"
@@ -1129,6 +1542,125 @@
resolved "https://registry.yarnpkg.com/@simplewebauthn/browser/-/browser-13.1.2.tgz#e904373854616e469c4c1ab9d8c4f704e9ac6db1"
integrity sha512-aZnW0KawAM83fSBUgglP5WofbrLbLyr7CoPqYr66Eppm7zO86YX6rrCjRB3hQKPrL7ATvY4FVXlykZ6w6FwYYw==
+"@standard-schema/utils@^0.3.0":
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/@standard-schema/utils/-/utils-0.3.0.tgz#3d5e608f16c2390c10528e98e59aef6bf73cae7b"
+ integrity sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==
+
+"@tailwindcss/node@4.3.0":
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/@tailwindcss/node/-/node-4.3.0.tgz#9dc5312bf41c48658529f36021e0b466c4eb7860"
+ integrity sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==
+ dependencies:
+ "@jridgewell/remapping" "^2.3.5"
+ enhanced-resolve "^5.21.0"
+ jiti "^2.6.1"
+ lightningcss "1.32.0"
+ magic-string "^0.30.21"
+ source-map-js "^1.2.1"
+ tailwindcss "4.3.0"
+
+"@tailwindcss/oxide-android-arm64@4.3.0":
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz#e4533b6125236fe81a899cf5a82028c85244def8"
+ integrity sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==
+
+"@tailwindcss/oxide-darwin-arm64@4.3.0":
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz#96b074ef64ec6c41d580063740c8d36cf5c459ce"
+ integrity sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==
+
+"@tailwindcss/oxide-darwin-x64@4.3.0":
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz#0d9638d06d38684339b2dc06631966a7296bb64e"
+ integrity sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==
+
+"@tailwindcss/oxide-freebsd-x64@4.3.0":
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz#efc7acd17cd38d7585c07cb938a4f1b703f79d7a"
+ integrity sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==
+
+"@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0":
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz#e41c945e529670cd93fd6ed0c6a2880de5c40333"
+ integrity sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==
+
+"@tailwindcss/oxide-linux-arm64-gnu@4.3.0":
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz#6bb608b16ba7146d61097c2f4c7ee927d1f3580a"
+ integrity sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==
+
+"@tailwindcss/oxide-linux-arm64-musl@4.3.0":
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz#1bb443aa371bb99b50cb39d4d688151fadcd8a63"
+ integrity sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==
+
+"@tailwindcss/oxide-linux-x64-gnu@4.3.0":
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz#5267c0bb2597426c0d2e759acb5389cde2aa71fd"
+ integrity sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==
+
+"@tailwindcss/oxide-linux-x64-musl@4.3.0":
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz#fb2da97c67b218e5c7c723cb32782d55d7e4a5d5"
+ integrity sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==
+
+"@tailwindcss/oxide-wasm32-wasi@4.3.0":
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz#3f6538e511066d67d8683863dcaeeb16c22de849"
+ integrity sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==
+ dependencies:
+ "@emnapi/core" "^1.10.0"
+ "@emnapi/runtime" "^1.10.0"
+ "@emnapi/wasi-threads" "^1.2.1"
+ "@napi-rs/wasm-runtime" "^1.1.4"
+ "@tybys/wasm-util" "^0.10.1"
+ tslib "^2.8.1"
+
+"@tailwindcss/oxide-win32-arm64-msvc@4.3.0":
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz#ec45fba773c76759338c05d4fe5cf42c4eea2e4e"
+ integrity sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==
+
+"@tailwindcss/oxide-win32-x64-msvc@4.3.0":
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz#58cdd6e06adbe2e3160274edfcd0b0b43e17fee4"
+ integrity sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==
+
+"@tailwindcss/oxide@4.3.0":
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/@tailwindcss/oxide/-/oxide-4.3.0.tgz#cc1c61e88f62c0e9f56062de3e7873acaa2159d4"
+ integrity sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==
+ optionalDependencies:
+ "@tailwindcss/oxide-android-arm64" "4.3.0"
+ "@tailwindcss/oxide-darwin-arm64" "4.3.0"
+ "@tailwindcss/oxide-darwin-x64" "4.3.0"
+ "@tailwindcss/oxide-freebsd-x64" "4.3.0"
+ "@tailwindcss/oxide-linux-arm-gnueabihf" "4.3.0"
+ "@tailwindcss/oxide-linux-arm64-gnu" "4.3.0"
+ "@tailwindcss/oxide-linux-arm64-musl" "4.3.0"
+ "@tailwindcss/oxide-linux-x64-gnu" "4.3.0"
+ "@tailwindcss/oxide-linux-x64-musl" "4.3.0"
+ "@tailwindcss/oxide-wasm32-wasi" "4.3.0"
+ "@tailwindcss/oxide-win32-arm64-msvc" "4.3.0"
+ "@tailwindcss/oxide-win32-x64-msvc" "4.3.0"
+
+"@tailwindcss/vite@^4.3.0":
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/@tailwindcss/vite/-/vite-4.3.0.tgz#b2bbc069a4c700ea7aef5ee30416d84b7652e136"
+ integrity sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==
+ dependencies:
+ "@tailwindcss/node" "4.3.0"
+ "@tailwindcss/oxide" "4.3.0"
+ tailwindcss "4.3.0"
+
+"@tybys/wasm-util@^0.10.1":
+ version "0.10.2"
+ resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.2.tgz#12b3a1b33db1f9cad4ddff1f604ab7dd00bf464e"
+ integrity sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==
+ dependencies:
+ tslib "^2.4.0"
+
"@types/babel__core@^7.20.5":
version "7.20.5"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"
@@ -1606,6 +2138,13 @@ argparse@^2.0.1:
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
+aria-hidden@^1.2.4:
+ version "1.2.6"
+ resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.6.tgz#73051c9b088114c795b1ea414e9c0fff874ffc1a"
+ integrity sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==
+ dependencies:
+ tslib "^2.0.0"
+
array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b"
@@ -1847,11 +2386,6 @@ callsites@^3.0.0:
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
-camelcase-css@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5"
- integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
-
caniuse-lite@^1.0.30001735:
version "1.0.30001736"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001736.tgz#3710a99cf154b653590fb6a57f81ee34173c3b47"
@@ -1916,6 +2450,13 @@ cjs-module-lexer@^1.2.2:
resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d"
integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==
+class-variance-authority@^0.7.1:
+ version "0.7.1"
+ resolved "https://registry.yarnpkg.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz#4008a798a0e4553a781a57ac5177c9fb5d043787"
+ integrity sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==
+ dependencies:
+ clsx "^2.1.1"
+
cliui@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
@@ -1930,6 +2471,16 @@ clsx@^2.0.0, clsx@^2.1.1:
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
+cmdk@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/cmdk/-/cmdk-1.1.1.tgz#b8524272699ccaa37aaf07f36850b376bf3d58e5"
+ integrity sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==
+ dependencies:
+ "@radix-ui/react-compose-refs" "^1.1.1"
+ "@radix-ui/react-dialog" "^1.1.6"
+ "@radix-ui/react-id" "^1.1.0"
+ "@radix-ui/react-primitive" "^2.0.2"
+
collapse-white-space@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-2.1.0.tgz#640257174f9f42c740b40f3b55ee752924feefca"
@@ -1988,16 +2539,6 @@ cross-spawn@^7.0.6:
shebang-command "^2.0.0"
which "^2.0.1"
-cssesc@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
- integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
-
-csstype@^3.0.2:
- version "3.1.3"
- resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
- integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
-
data-view-buffer@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz#211a03ba95ecaf7798a8c7198d79536211f88570"
@@ -2101,6 +2642,11 @@ dequal@^2.0.0:
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
+detect-libc@^2.0.3:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad"
+ integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==
+
detect-node-es@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493"
@@ -2125,14 +2671,6 @@ doctrine@^2.1.0:
dependencies:
esutils "^2.0.2"
-dom-helpers@^5.0.1:
- version "5.2.1"
- resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
- integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
- dependencies:
- "@babel/runtime" "^7.8.7"
- csstype "^3.0.2"
-
dunder-proto@^1.0.0, dunder-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
@@ -2174,6 +2712,14 @@ emoji-regex@^9.2.2:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
+enhanced-resolve@^5.21.0:
+ version "5.23.0"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.23.0.tgz#dfdf8d1c9065e4b52f8a598356138931c07305f9"
+ integrity sha512-yJN/BOOLxcOW2aQgeif9mSnaUB8KtvmMMp56oA1kx1CRfBKbhZm2pJ+NBY+3eOboHxix8lfjWpHE0Ei5U8RbSA==
+ dependencies:
+ graceful-fs "^4.2.4"
+ tapable "^2.3.3"
+
err-code@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9"
@@ -2993,7 +3539,7 @@ gopd@^1.0.1, gopd@^1.2.0:
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
-graceful-fs@^4.1.2:
+graceful-fs@^4.1.2, graceful-fs@^4.2.4:
version "4.2.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
@@ -3298,6 +3844,11 @@ inline-style-parser@0.2.4:
resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.4.tgz#f4af5fe72e612839fcd453d989a586566d695f22"
integrity sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==
+input-otp@^1.4.2:
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/input-otp/-/input-otp-1.4.2.tgz#f4d3d587d0f641729e55029b3b8c4870847f4f07"
+ integrity sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==
+
internal-slot@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961"
@@ -3590,6 +4141,11 @@ jackspeak@^3.1.2:
optionalDependencies:
"@pkgjs/parseargs" "^0.11.0"
+jiti@^2.6.1:
+ version "2.7.0"
+ resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.7.0.tgz#974228f2f4ca2bc21885a1797b45fea68e950c64"
+ integrity sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==
+
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -3690,11 +4246,6 @@ 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.6:
- version "2.0.6"
- resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22"
- integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==
-
less@^4.3.0:
version "4.4.1"
resolved "https://registry.yarnpkg.com/less/-/less-4.4.1.tgz#2f97168bf887ca6a9957ee69e16cc34f8b007cc7"
@@ -3720,6 +4271,80 @@ levn@^0.4.1:
prelude-ls "^1.2.1"
type-check "~0.4.0"
+lightningcss-android-arm64@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.yarnpkg.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz#f033885116dfefd9c6f54787523e3514b61e1968"
+ integrity sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==
+
+lightningcss-darwin-arm64@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz#50b71871b01c8199584b649e292547faea7af9b5"
+ integrity sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==
+
+lightningcss-darwin-x64@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz#35f3e97332d130b9ca181e11b568ded6aebc6d5e"
+ integrity sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==
+
+lightningcss-freebsd-x64@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz#9777a76472b64ed6ff94342ad64c7bafd794a575"
+ integrity sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==
+
+lightningcss-linux-arm-gnueabihf@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz#13ae652e1ab73b9135d7b7da172f666c410ad53d"
+ integrity sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==
+
+lightningcss-linux-arm64-gnu@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz#417858795a94592f680123a1b1f9da8a0e1ef335"
+ integrity sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==
+
+lightningcss-linux-arm64-musl@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz#6be36692e810b718040802fd809623cffe732133"
+ integrity sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==
+
+lightningcss-linux-x64-gnu@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz#0b7803af4eb21cfd38dd39fe2abbb53c7dd091f6"
+ integrity sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==
+
+lightningcss-linux-x64-musl@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz#88dc8ba865ddddb1ac5ef04b0f161804418c163b"
+ integrity sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==
+
+lightningcss-win32-arm64-msvc@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz#4f30ba3fa5e925f5b79f945e8cc0d176c3b1ab38"
+ integrity sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==
+
+lightningcss-win32-x64-msvc@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz#141aa5605645064928902bb4af045fa7d9f4220a"
+ integrity sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==
+
+lightningcss@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.yarnpkg.com/lightningcss/-/lightningcss-1.32.0.tgz#b85aae96486dcb1bf49a7c8571221273f4f1e4a9"
+ integrity sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==
+ dependencies:
+ detect-libc "^2.0.3"
+ optionalDependencies:
+ lightningcss-android-arm64 "1.32.0"
+ lightningcss-darwin-arm64 "1.32.0"
+ lightningcss-darwin-x64 "1.32.0"
+ lightningcss-freebsd-x64 "1.32.0"
+ lightningcss-linux-arm-gnueabihf "1.32.0"
+ lightningcss-linux-arm64-gnu "1.32.0"
+ lightningcss-linux-arm64-musl "1.32.0"
+ lightningcss-linux-x64-gnu "1.32.0"
+ lightningcss-linux-x64-musl "1.32.0"
+ lightningcss-win32-arm64-msvc "1.32.0"
+ lightningcss-win32-x64-msvc "1.32.0"
+
lines-and-columns@^2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-2.0.4.tgz#d00318855905d2660d8c0822e3f5a4715855fc42"
@@ -3789,6 +4414,11 @@ lru-cache@^5.1.1:
dependencies:
yallist "^3.0.2"
+lucide-react@^1.17.0:
+ version "1.17.0"
+ resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-1.17.0.tgz#5b8ddd7d6975e3e45dc9d03025afc7674d78f8a8"
+ integrity sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w==
+
magic-string@^0.30.17:
version "0.30.17"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453"
@@ -3796,6 +4426,13 @@ magic-string@^0.30.17:
dependencies:
"@jridgewell/sourcemap-codec" "^1.5.0"
+magic-string@^0.30.21:
+ version "0.30.21"
+ resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91"
+ integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==
+ dependencies:
+ "@jridgewell/sourcemap-codec" "^1.5.5"
+
make-dir@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
@@ -4739,51 +5376,6 @@ possible-typed-array-names@^1.0.0:
resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae"
integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==
-postcss-js@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2"
- integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==
- dependencies:
- camelcase-css "^2.0.1"
-
-postcss-mixins@^12.0.0:
- version "12.1.2"
- resolved "https://registry.yarnpkg.com/postcss-mixins/-/postcss-mixins-12.1.2.tgz#8a82ccad16eb60525ebcd2a8eec5721230a9ca14"
- integrity sha512-90pSxmZVfbX9e5xCv7tI5RV1mnjdf16y89CJKbf/hD7GyOz1FCxcYMl8ZYA8Hc56dbApTKKmU9HfvgfWdCxlwg==
- dependencies:
- postcss-js "^4.0.1"
- postcss-simple-vars "^7.0.1"
- sugarss "^5.0.0"
- tinyglobby "^0.2.14"
-
-postcss-nested@^7.0.2:
- version "7.0.2"
- resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-7.0.2.tgz#863d83a6b5df0a2894560394be93d5383ea37a65"
- integrity sha512-5osppouFc0VR9/VYzYxO03VaDa3e8F23Kfd6/9qcZTUI8P58GIYlArOET2Wq0ywSl2o2PjELhYOFI4W7l5QHKw==
- dependencies:
- postcss-selector-parser "^7.0.0"
-
-postcss-preset-mantine@^1.17.0:
- version "1.18.0"
- resolved "https://registry.yarnpkg.com/postcss-preset-mantine/-/postcss-preset-mantine-1.18.0.tgz#e665fab8205b69f27d634e19d9a6524a2fb1fc04"
- integrity sha512-sP6/s1oC7cOtBdl4mw/IRKmKvYTuzpRrH/vT6v9enMU/EQEQ31eQnHcWtFghOXLH87AAthjL/Q75rLmin1oZoA==
- dependencies:
- postcss-mixins "^12.0.0"
- postcss-nested "^7.0.2"
-
-postcss-selector-parser@^7.0.0:
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz#4d6af97eba65d73bc4d84bcb343e865d7dd16262"
- integrity sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==
- dependencies:
- cssesc "^3.0.0"
- util-deprecate "^1.0.2"
-
-postcss-simple-vars@^7.0.1:
- version "7.0.1"
- resolved "https://registry.yarnpkg.com/postcss-simple-vars/-/postcss-simple-vars-7.0.1.tgz#836b3097a54dcd13dbd3c36a5dbdd512fad2954c"
- integrity sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A==
-
postcss@^8.5.3, postcss@^8.5.6:
version "8.5.6"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c"
@@ -4834,7 +5426,7 @@ promise-retry@^2.0.1:
err-code "^2.0.2"
retry "^0.12.0"
-prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
+prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -4902,10 +5494,10 @@ react-dom@^19.1.1:
dependencies:
scheduler "^0.26.0"
-react-dropzone@^14.3.8:
- version "14.3.8"
- resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-14.3.8.tgz#a7eab118f8a452fe3f8b162d64454e81ba830582"
- integrity sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==
+react-dropzone@^15.0.0:
+ version "15.0.0"
+ resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-15.0.0.tgz#bd03c7c2b14fe4ea9db1a9c74502b85339f2e505"
+ integrity sha512-lGjYV/EoqEjEWPnmiSvH4v5IoIAwQM2W4Z1C0Q/Pw2xD0eVzKPS359BQTUMum+1fa0kH2nrKjuavmTPOGhpLPg==
dependencies:
attr-accept "^2.2.4"
file-selector "^2.1.0"
@@ -4927,6 +5519,11 @@ react-helmet-async@^1.3.0:
react-fast-compare "^3.2.0"
shallowequal "^1.1.0"
+react-hook-form@^7.77.0:
+ version "7.77.0"
+ resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.77.0.tgz#af7e1d90d00c0418a98cf00b6f9bd5b85587c6fb"
+ integrity sha512-Sslh9YDYc0GDlWT/lxasnIduNo4v3yyvqRGvmGKUre5AFjDs/HV9/OafHGD8d+sB2yoL4UIL9L8X9i0WlZZebg==
+
react-icons@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.5.0.tgz#8aa25d3543ff84231685d3331164c00299cdfaf2"
@@ -4954,11 +5551,6 @@ react-markdown@^10.1.0:
unist-util-visit "^5.0.0"
vfile "^6.0.0"
-react-number-format@^5.4.4:
- version "5.4.4"
- resolved "https://registry.yarnpkg.com/react-number-format/-/react-number-format-5.4.4.tgz#d31f0e260609431500c8d3f81bbd3ae1fb7cacad"
- integrity sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA==
-
react-refresh@^0.17.0:
version "0.17.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.17.0.tgz#b7e579c3657f23d04eccbe4ad2e58a8ed51e7e53"
@@ -4972,7 +5564,7 @@ react-remove-scroll-bar@^2.3.7:
react-style-singleton "^2.2.2"
tslib "^2.0.0"
-react-remove-scroll@^2.7.1:
+react-remove-scroll@^2.6.3:
version "2.7.2"
resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz#6442da56791117661978ae99cd29be9026fecca0"
integrity sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==
@@ -4991,25 +5583,6 @@ react-style-singleton@^2.2.2, react-style-singleton@^2.2.3:
get-nonce "^1.0.0"
tslib "^2.0.0"
-react-textarea-autosize@8.5.9:
- version "8.5.9"
- resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.5.9.tgz#ab8627b09aa04d8a2f45d5b5cd94c84d1d4a8893"
- integrity sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==
- dependencies:
- "@babel/runtime" "^7.20.13"
- use-composed-ref "^1.3.0"
- use-latest "^1.2.1"
-
-react-transition-group@4.4.5:
- version "4.4.5"
- resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
- integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==
- dependencies:
- "@babel/runtime" "^7.5.5"
- dom-helpers "^5.0.1"
- loose-envify "^1.4.0"
- prop-types "^15.6.2"
-
react@^19.1.1:
version "19.1.1"
resolved "https://registry.yarnpkg.com/react/-/react-19.1.1.tgz#06d9149ec5e083a67f9a1e39ce97b06a03b644af"
@@ -5469,6 +6042,11 @@ signal-exit@^4.0.1:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
+sonner@^2.0.7:
+ version "2.0.7"
+ resolved "https://registry.yarnpkg.com/sonner/-/sonner-2.0.7.tgz#810c1487a67ec3370126e0f400dfb9edddc3e4f6"
+ integrity sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==
+
source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
@@ -5702,11 +6280,6 @@ style-to-object@1.0.9:
dependencies:
inline-style-parser "0.2.4"
-sugarss@^5.0.0:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/sugarss/-/sugarss-5.0.1.tgz#55589d1b997cdb1f0d84a94ed7f002e708f909bc"
- integrity sha512-ctS5RYCBVvPoZAnzIaX5QSShK8ZiZxD5HUqSxlusvEMC+QZQIPCPOIJg6aceFX+K2rf4+SH89eu++h1Zmsr2nw==
-
supports-color@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
@@ -5731,10 +6304,20 @@ synckit@^0.11.8:
dependencies:
"@pkgr/core" "^0.2.9"
-tabbable@^6.0.0:
- version "6.2.0"
- resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97"
- integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==
+tailwind-merge@^3.6.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-3.6.0.tgz#88d83242d1dd7bc847223f73dcf210dd1f2ee11c"
+ integrity sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==
+
+tailwindcss@4.3.0, tailwindcss@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-4.3.0.tgz#0a874e044a859cf6de413f3a59e76a9bedf05264"
+ integrity sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==
+
+tapable@^2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.3.tgz#5da7c9992c46038221267985ab28421a8879f160"
+ integrity sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==
tinybench@^2.9.0:
version "2.9.0"
@@ -5824,11 +6407,16 @@ tsconfig-paths@^3.15.0:
minimist "^1.2.6"
strip-bom "^3.0.0"
-tslib@^2.0.0, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.7.0:
+tslib@^2.0.0, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.7.0, tslib@^2.8.1:
version "2.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
+tw-animate-css@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/tw-animate-css/-/tw-animate-css-1.4.0.tgz#b4a06f68244cba39428aa47e65e6e4c0babc21ee"
+ integrity sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==
+
type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
@@ -5841,11 +6429,6 @@ type-fest@^3.8.0:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.13.1.tgz#bb744c1f0678bea7543a2d1ec24e83e68e8c8706"
integrity sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==
-type-fest@^4.41.0:
- version "4.41.0"
- resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58"
- integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==
-
typed-array-buffer@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536"
@@ -6097,23 +6680,6 @@ use-callback-ref@^1.3.3:
dependencies:
tslib "^2.0.0"
-use-composed-ref@^1.3.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.4.0.tgz#09e023bf798d005286ad85cd20674bdf5770653b"
- integrity sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==
-
-use-isomorphic-layout-effect@^1.1.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz#2f11a525628f56424521c748feabc2ffcc962fce"
- integrity sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==
-
-use-latest@^1.2.1:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.3.0.tgz#549b9b0d4c1761862072f0899c6f096eb379137a"
- integrity sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==
- dependencies:
- use-isomorphic-layout-effect "^1.1.1"
-
use-sidecar@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.3.tgz#10e7fd897d130b896e2c546c63a5e8233d00efdb"
@@ -6122,7 +6688,12 @@ use-sidecar@^1.1.3:
detect-node-es "^1.1.0"
tslib "^2.0.0"
-util-deprecate@^1.0.1, util-deprecate@^1.0.2:
+use-sync-external-store@^1.5.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d"
+ integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==
+
+util-deprecate@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
@@ -6450,6 +7021,11 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
+zod@^4.4.3:
+ version "4.4.3"
+ resolved "https://registry.yarnpkg.com/zod/-/zod-4.4.3.tgz#b680f172885d18bbebf21a834ea25e55a1bbf356"
+ integrity sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==
+
zwitch@^2.0.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7"