initial slack build out (no apis)

This commit is contained in:
Nate Kelley 2025-07-09 11:46:27 -06:00
parent 12fa022cfe
commit 66cae400d6
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
19 changed files with 130 additions and 37 deletions

View File

@ -7,7 +7,7 @@ import { Button } from '@/components/ui/buttons';
import { Plus } from '@/components/ui/icons';
import { useMemoizedFn } from '@/hooks';
import { useDebounceSearch } from '@/hooks/useDebounceSearch';
import { SettingsPageHeader } from '../../_components/SettingsPageHeader';
import { SettingsPageHeader } from '@/components/features/settings';
import { ListDatasetGroupsComponent } from './ListDatasetGroupsComponent';
export default function Page() {

View File

@ -10,7 +10,7 @@ import { Button } from '@/components/ui/buttons';
import { Plus } from '@/components/ui/icons';
import { useMemoizedFn } from '@/hooks';
import { useDebounceSearch } from '@/hooks/useDebounceSearch';
import { SettingsPageHeader } from '../../_components/SettingsPageHeader';
import { SettingsPageHeader } from '@/components/features/settings';
import { ListPermissionGroupsComponent } from './ListPermissionGroupsComponent';
export default function Page() {

View File

@ -9,7 +9,7 @@ import { useInviteModalStore } from '@/context/BusterAppLayout';
import { useUserConfigContextSelector } from '@/context/Users';
import { useMemoizedFn } from '@/hooks';
import { useDebounceSearch } from '@/hooks/useDebounceSearch';
import { SettingsPageHeader } from '../../_components/SettingsPageHeader';
import { SettingsPageHeader } from '@/components/features/settings';
import { ListUsersComponent } from './ListUsersComponent';
export default function Page() {

View File

@ -1,4 +1,4 @@
import { SettingsPageHeader } from '../../../_components/SettingsPageHeader';
import { SettingsPageHeader } from '@/components/features/settings';
import { ApiKeysController } from './ApiKeysController';
import type { Metadata } from 'next';

View File

@ -1,4 +1,4 @@
import { SettingsPageHeader } from '../../../_components/SettingsPageHeader';
import { SettingsPageHeader } from '@/components/features/settings';
import { DatasourceList } from './_DatasourceList';
export default function Page() {

View File

@ -0,0 +1,18 @@
import { SlackIntegrations } from '@/components/features/integrations/SlackIntegrations';
import { SettingsPageHeader } from '@/components/features/settings';
import { SettingsCards } from '@/components/features/settings';
export default function IntegrationsPage() {
return (
<>
<SettingsPageHeader
title="Integrations"
description="Connect Buster with other apps and services"
/>
<div className="flex flex-col space-y-6">
<SlackIntegrations />
</div>
</>
);
}

View File

@ -1,23 +1,21 @@
import { InviteLinks } from '@/components/features/security/InviteLinks';
import { SettingsPageHeader } from '../../../_components/SettingsPageHeader';
import { SettingsPageHeader } from '@/components/features/settings';
import { ApprovedEmailDomains } from '@/components/features/security/ApprovedEmailDomains';
import { WorkspaceRestrictions } from '@/components/features/security/WorkspaceRestrictions';
export default function Page() {
return (
<div className="flex flex-col space-y-4">
<div>
<SettingsPageHeader
title="Security"
description="Manage security and general permission settings"
/>
<>
<SettingsPageHeader
title="Security"
description="Manage security and general permission settings"
/>
<div className="flex flex-col space-y-6">
<InviteLinks />
<ApprovedEmailDomains />
<WorkspaceRestrictions />
</div>
<div className="flex flex-col space-y-6">
<InviteLinks />
<ApprovedEmailDomains />
<WorkspaceRestrictions />
</div>
</div>
</>
);
}

View File

@ -4,7 +4,7 @@ import { Avatar } from '@/components/ui/avatar';
import { Text, Title } from '@/components/ui/typography';
import { useUserConfigContextSelector } from '@/context/Users/BusterUserConfigProvider';
import { formatDate } from '@/lib/date';
import { SettingsPageHeader } from '../../_components/SettingsPageHeader';
import { SettingsPageHeader } from '@/components/features/settings';
export default function ProfilePage() {
const user = useUserConfigContextSelector((state) => state.user);

View File

@ -0,0 +1,38 @@
import React from 'react';
import { SettingsCards } from '../settings/SettingsCard';
import { SlackIcon } from '@/components/ui/icons/customIcons/SlackIcon';
import { Text } from '@/components/ui/typography';
import { Button } from '@/components/ui/buttons';
export const SlackIntegrations = React.memo(() => {
return (
<SettingsCards
title="Slack"
description="Connect Buster with Slack"
cards={[{ sections: [<ConnectSlackCard />] }]}
/>
);
});
SlackIntegrations.displayName = 'SlackIntegrations';
const ConnectSlackCard = React.memo(() => {
return (
<div className="flex items-center justify-between gap-x-2">
<div className="flex space-x-2">
<div className="bg-item-select flex items-center justify-center rounded p-2">
<SlackIcon size={16} />
</div>
<div className="flex flex-col space-y-0.5">
<Text>Slack account</Text>
<Text variant="secondary" size={'xs'}>
Link your slack account to use Buster from Slack
</Text>
</div>
</div>
<Button prefix={<SlackIcon size={16} />} size={'tall'}>
Connect Slack
</Button>
</div>
);
});

View File

@ -1,7 +1,7 @@
'use client';
import React, { useMemo, useState } from 'react';
import { SecurityCards } from './SecurityCards';
import { SettingsCards } from '../settings/SettingsCard';
import { Input } from '@/components/ui/inputs';
import { Button } from '@/components/ui/buttons';
import { Text } from '@/components/ui/typography';
@ -71,7 +71,7 @@ export const ApprovedEmailDomains = React.memo(() => {
);
return (
<SecurityCards
<SettingsCards
title="Approved email domains"
description="Anyone with an email address at these domains is allowed to sign up for this workspace"
cards={[{ sections }]}
@ -91,7 +91,7 @@ const AddDomainInput = React.memo(
const handleAddDomain = useMemoizedFn(async () => {
const domain = newDomain.trim();
if (!domain) return;
const domainRegex = /^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]\.[a-zA-Z]{2,}$/;
if (!domainRegex.test(domain)) {
openErrorMessage('Please enter a valid domain name');

View File

@ -1,7 +1,7 @@
'use client';
import React from 'react';
import { SecurityCards } from './SecurityCards';
import { SettingsCards } from '../settings/SettingsCard';
import { Switch } from '@/components/ui/switch';
import { Input } from '@/components/ui/inputs';
import { Button } from '@/components/ui/buttons';
@ -34,7 +34,7 @@ export const InviteLinks = React.memo(() => {
};
return (
<SecurityCards
<SettingsCards
title="Invite links"
description="A uniquely generated invite link allows anyone with the link to join your workspace"
cards={[

View File

@ -1,9 +1,8 @@
'use client';
import React, { useMemo, type ReactNode } from 'react';
import { SecurityCards } from './SecurityCards';
import { SettingsCards } from '../settings/SettingsCard';
import { Text } from '@/components/ui/typography';
import { Button } from '@/components/ui/buttons';
import { Switch } from '@/components/ui/switch';
import {
useGetWorkspaceSettings,
@ -45,7 +44,7 @@ export const WorkspaceRestrictions = React.memo(() => {
);
return (
<SecurityCards
<SettingsCards
title="Workspace restrictions"
description="Restrict the workspace to only allow users with an email address at these domains"
cards={[{ sections }]}

View File

@ -1,12 +1,12 @@
import type { Meta, StoryObj } from '@storybook/react';
import { SecurityCards } from './SecurityCards';
import { SettingsCards } from './SettingsCard';
import { Button } from '@/components/ui/buttons';
import { Pill } from '@/components/ui/pills/Pill';
import { Text } from '@/components/ui/typography';
const meta: Meta<typeof SecurityCards> = {
title: 'Features/SecurityCards',
component: SecurityCards,
const meta: Meta<typeof SettingsCards> = {
title: 'Features/SettingsCards',
component: SettingsCards,
parameters: {
layout: 'padded',
docs: {
@ -20,7 +20,7 @@ const meta: Meta<typeof SecurityCards> = {
};
export default meta;
type Story = StoryObj<typeof SecurityCards>;
type Story = StoryObj<typeof SettingsCards>;
// Mock data for different use cases
const basicSections = [

View File

@ -2,7 +2,7 @@ import React from 'react';
import { Title, Paragraph } from '@/components/ui/typography';
import { cn } from '@/lib/classMerge';
interface SecurityCardsProps {
interface SettingsCardsProps {
title: string;
description: string;
cards: {
@ -10,7 +10,7 @@ interface SecurityCardsProps {
}[];
}
export const SecurityCards: React.FC<SecurityCardsProps> = ({ title, description, cards }) => {
export const SettingsCards: React.FC<SettingsCardsProps> = ({ title, description, cards }) => {
return (
<div className="flex flex-col space-y-3.5">
<div className="flex flex-col space-y-1.5">
@ -20,17 +20,17 @@ export const SecurityCards: React.FC<SecurityCardsProps> = ({ title, description
<Paragraph variant="secondary">{description}</Paragraph>
</div>
{cards.map((card, index) => (
<SecurityCard key={index} sections={card.sections} />
<SettingsCard key={index} sections={card.sections} />
))}
</div>
);
};
const SecurityCard = ({ sections }: { sections: React.ReactNode[] }) => {
const SettingsCard = ({ sections }: { sections: React.ReactNode[] }) => {
return (
<div className="flex flex-col rounded border">
{sections.map((section, index) => (
<div key={index} className={cn(index !== sections.length - 1 && 'border-b', 'px-4 py-2.5')}>
<div key={index} className={cn(index !== sections.length - 1 && 'border-b', 'px-4 py-3.5')}>
{section}
</div>
))}

View File

@ -1,2 +1,3 @@
export * from './SettingsEmptyState';
export * from './SettingsPageHeader';
export * from './SettingsCard';

View File

@ -39,6 +39,11 @@ const workspaceItems = (currentParentRoute: BusterRoutes): ISidebarGroup => ({
label: 'Data Sources',
route: createBusterRoute({ route: BusterRoutes.SETTINGS_DATASOURCES }),
id: BusterRoutes.SETTINGS_DATASOURCES
},
{
label: 'Integrations',
route: createBusterRoute({ route: BusterRoutes.SETTINGS_INTEGRATIONS }),
id: createBusterRoute({ route: BusterRoutes.SETTINGS_INTEGRATIONS })
}
].map((item) => ({
...item,

View File

@ -0,0 +1,34 @@
import React from 'react';
export const SlackIcon: React.FC<
React.SVGProps<SVGSVGElement> & {
size?: number;
}
> = ({ className, size = 24, ...props }) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 127 127"
xmlns="http://www.w3.org/2000/svg"
className={className}
{...props}>
<path
d="M27.2 80c0 7.3-5.9 13.2-13.2 13.2C6.7 93.2.8 87.3.8 80c0-7.3 5.9-13.2 13.2-13.2h13.2V80zm6.6 0c0-7.3 5.9-13.2 13.2-13.2 7.3 0 13.2 5.9 13.2 13.2v33c0 7.3-5.9 13.2-13.2 13.2-7.3 0-13.2-5.9-13.2-13.2V80z"
fill="#E01E5A"
/>
<path
d="M47 27c-7.3 0-13.2-5.9-13.2-13.2C33.8 6.5 39.7.6 47 .6c7.3 0 13.2 5.9 13.2 13.2V27H47zm0 6.7c7.3 0 13.2 5.9 13.2 13.2 0 7.3-5.9 13.2-13.2 13.2H13.9C6.6 60.1.7 54.2.7 46.9c0-7.3 5.9-13.2 13.2-13.2H47z"
fill="#36C5F0"
/>
<path
d="M99.9 46.9c0-7.3 5.9-13.2 13.2-13.2 7.3 0 13.2 5.9 13.2 13.2 0 7.3-5.9 13.2-13.2 13.2H99.9V46.9zm-6.6 0c0 7.3-5.9 13.2-13.2 13.2-7.3 0-13.2-5.9-13.2-13.2V13.8C66.9 6.5 72.8.6 80.1.6c7.3 0 13.2 5.9 13.2 13.2v33.1z"
fill="#2EB67D"
/>
<path
d="M80.1 99.8c7.3 0 13.2 5.9 13.2 13.2 0 7.3-5.9 13.2-13.2 13.2-7.3 0-13.2-5.9-13.2-13.2V99.8h13.2zm0-6.6c-7.3 0-13.2-5.9-13.2-13.2 0-7.3 5.9-13.2 13.2-13.2h33.1c7.3 0 13.2 5.9 13.2 13.2 0 7.3-5.9 13.2-13.2 13.2H80.1z"
fill="#ECB22E"
/>
</svg>
);
};