update all types to work better together

This commit is contained in:
Nate Kelley 2025-07-17 18:34:01 -06:00
parent 09e4b36bf5
commit 10e37f07de
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
28 changed files with 290 additions and 143 deletions

View File

@ -347,6 +347,7 @@ pub struct Organization {
pub domains: Option<Vec<String>>,
pub restrict_new_user_invitations: bool,
pub default_role: UserOrganizationRole,
pub organization_color_palettes: serde_json::Value,
}
#[derive(
@ -727,6 +728,22 @@ pub struct UserConfig {
pub last_used_color_palette: Option<Vec<String>>,
}
/// Organization Color Palette Types
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct OrganizationColorPalette {
pub id: String,
pub colors: Vec<String>, // Hex color codes
pub name: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct OrganizationColorPalettes {
pub selected_id: Option<String>,
pub palettes: Vec<OrganizationColorPalette>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub enum StepProgress {

View File

@ -452,6 +452,7 @@ diesel::table! {
domains -> Nullable<Array<Text>>,
restrict_new_user_invitations -> Bool,
default_role -> UserOrganizationRoleEnum,
organization_color_palettes -> Jsonb,
}
}

View File

@ -37,6 +37,10 @@ pub async fn post_organization_handler(name: String, user: AuthenticatedUser) ->
domains: None,
restrict_new_user_invitations: false,
default_role: UserOrganizationRole::RestrictedQuerier,
organization_color_palettes: serde_json::json!({
"selectedId": null,
"palettes": []
}),
};
insert_into(organizations::table)

View File

@ -123,6 +123,7 @@ pub async fn get_user_information(user_id: &Uuid) -> Result<UserInfoObject> {
organizations::domains,
organizations::restrict_new_user_invitations,
organizations::default_role,
organizations::organization_color_palettes,
)
.nullable(),
users_to_organizations::role.nullable(),

View File

@ -413,6 +413,7 @@ pub async fn get_user_information(user_id: &Uuid) -> Result<UserInfoObject> {
organizations::domains,
organizations::restrict_new_user_invitations,
organizations::default_role,
organizations::organization_color_palettes,
)
.nullable(),
users_to_organizations::role.nullable(),

View File

@ -2,6 +2,10 @@
"$schema": "https://turbo.build/schema.json",
"extends": ["//"],
"tasks": {
"dev": {}
"dev": {
"dependsOn": ["@buster/database#start"],
"with": [],
"outputs": []
}
}
}

View File

@ -1,4 +1,4 @@
import type { OrganizationColorPalette } from '@buster/server-shared/organization';
import type { ColorPalette, OrganizationColorPalette } from '@buster/server-shared/organization';
export const DEFAULT_CHART_THEME = [
'#B399FD',
@ -321,7 +321,7 @@ const PINK_THEME = [
'#ad1457',
];
export const COLORFUL_THEMES: OrganizationColorPalette[] = [
export const COLORFUL_THEMES = [
{
name: 'Buster',
colors: DEFAULT_CHART_THEME,
@ -372,7 +372,7 @@ export const COLORFUL_THEMES: OrganizationColorPalette[] = [
colors: EMERALD_SPECTRUM_THEME,
},
{
name: 'Forest Lake',
name: 'Deep Forest',
colors: DIVERSE_DARK_PALETTE_GREEN_THEME,
},
{
@ -384,7 +384,7 @@ export const COLORFUL_THEMES: OrganizationColorPalette[] = [
id: theme.name,
}));
export const MONOCHROME_THEMES: OrganizationColorPalette[] = [
export const MONOCHROME_THEMES = [
{
name: 'Greens',
colors: GREENS_THEME,
@ -395,7 +395,7 @@ export const MONOCHROME_THEMES: OrganizationColorPalette[] = [
colors: BLUE_TO_ORANGE_GRADIENT,
},
{
name: 'Forest Lake',
name: 'Forest Sunset',
colors: FOREST_LAKE_GRADIENT,
},
{
@ -435,4 +435,14 @@ export const MONOCHROME_THEMES: OrganizationColorPalette[] = [
id: theme.name,
}));
export const ALL_THEMES: OrganizationColorPalette[] = [...COLORFUL_THEMES, ...MONOCHROME_THEMES];
const simplifyId = (name: string, index: number) => {
return `${name.toLowerCase().replace(/ /g, '-')}-${index}`;
};
export const ALL_THEMES: ColorPalette[] = [...COLORFUL_THEMES, ...MONOCHROME_THEMES].map(
(theme, index) => ({
colors: theme.colors,
name: theme.name,
id: simplifyId(theme.name, index),
})
);

View File

@ -3,6 +3,7 @@ import { Hono } from 'hono';
import healthcheckRoutes from '../healthcheck';
import chatsRoutes from './chats';
import currencyRoutes from './currency';
import dictionariesRoutes from './dictionaries';
import electricShapeRoutes from './electric-shape';
import organizationRoutes from './organization';
import securityRoutes from './security';
@ -19,6 +20,7 @@ const app = new Hono()
.route('/currency', currencyRoutes)
.route('/support', supportRoutes)
.route('/security', securityRoutes)
.route('/organizations', organizationRoutes);
.route('/organizations', organizationRoutes)
.route('/dictionaries', dictionariesRoutes);
export default app;

View File

@ -63,7 +63,10 @@ export async function createTestOrganizationInDb(
deletedAt: null,
domain: null,
paymentRequired: true,
organizationColorPalettes: [],
organizationColorPalettes: {
selectedId: null,
palettes: [],
},
...orgData,
};

View File

@ -51,7 +51,7 @@ export const useUpdateOrganization = () => {
queryClient.setQueryData(userQueryKey, (prev) => {
if (!prev) return prev;
return create(prev, (draft) => {
const newOrganization = create(prev, (draft) => {
if (
draft.organizations &&
Array.isArray(draft.organizations) &&
@ -60,6 +60,8 @@ export const useUpdateOrganization = () => {
Object.assign(draft.organizations[0], organizationUpdates);
}
});
return newOrganization;
});
},
onSuccess: () => {

View File

@ -3,7 +3,6 @@ import { DashboardLayout } from '@/layouts/DashboardLayout';
export default async function Layout({
children,
params,
...rest
}: {
children: React.ReactNode;
params: Promise<{ dashboardId: string }>;

View File

@ -1,4 +1,4 @@
import React, { type PropsWithChildren } from 'react';
import React, { useRef, type PropsWithChildren } from 'react';
import { ThemeList, type IColorTheme } from '../ThemeList';
import { Button } from '@/components/ui/buttons';
import { Plus } from '../../../ui/icons';
@ -50,8 +50,7 @@ export const AddCustomThemeBase = React.memo(
}: AddCustomThemeBaseProps) => {
const iThemes: Required<IColorTheme>[] = customThemes.map((theme) => ({
...theme,
selected: theme.id === selectedThemeId,
id: theme.id
selected: theme.id === selectedThemeId
}));
return (
@ -60,12 +59,14 @@ export const AddCustomThemeBase = React.memo(
deleteCustomTheme={deleteCustomTheme}
modifyCustomTheme={modifyCustomTheme}>
<div className="bg-item-select flex flex-col space-y-1 rounded border p-1">
{iThemes.length > 0 && (
<ThemeList
className="border-none bg-transparent p-0"
themes={iThemes}
onChangeColorTheme={onSelectTheme}
themeThreeDotsMenu={ThreeDotMenu}
/>
)}
<AddCustomThemeButton />
</div>
@ -87,25 +88,37 @@ const ThreeDotMenu: React.FC<{ theme: IColorTheme }> = React.memo(({ theme }) =>
await deleteCustomTheme(themeId);
});
return <NewThemePopup selectedTheme={theme} onSave={onSave} onDelete={onDelete} />;
const onUpdate = useMemoizedFn(async (theme: IColorTheme) => {
await modifyCustomTheme(theme.id, theme);
});
return (
<NewThemePopup selectedTheme={theme} onSave={onSave} onDelete={onDelete} onUpdate={onUpdate} />
);
});
ThreeDotMenu.displayName = 'ThreeDotMenu';
const AddCustomThemeButton: React.FC<{}> = React.memo(({}) => {
const AddCustomThemeButton: React.FC = React.memo(({}) => {
const { createCustomTheme } = useAddTheme();
const buttonRef = useRef<HTMLButtonElement>(null);
const closePopover = useMemoizedFn(() => {
buttonRef.current?.click();
});
const onSave = useMemoizedFn(async (theme: IColorTheme) => {
await createCustomTheme(theme);
closePopover();
});
return (
<Popover
content={<NewThemePopup selectedTheme={undefined} onSave={onSave} onDelete={undefined} />}
trigger="click"
className="p-0"
className="max-w-[320px] p-0"
sideOffset={12}>
<Button variant={'ghost'} size={'tall'} prefix={<Plus />}>
<Button ref={buttonRef} variant={'ghost'} size={'tall'} prefix={<Plus />}>
Add a custom theme
</Button>
</Popover>

View File

@ -8,31 +8,34 @@ import { useColorThemes } from '@/api/buster_rest/dictionaries';
import { StatusCard } from '@/components/ui/card/StatusCard';
import { CircleSpinnerLoader } from '../../../ui/loaders';
export const DefaultThemeSelector = React.memo(() => {
export const DefaultThemeSelector = React.memo(
({ className, themeListClassName }: { className?: string; themeListClassName?: string }) => {
const { data: userData } = useGetMyUserInfo();
const { data: themes, isFetched: isFetchedThemes, isError: isErrorThemes } = useColorThemes();
const { mutateAsync: updateOrganization } = useUpdateOrganization();
const organization = userData?.organizations?.[0]!;
const organization = userData?.organizations?.[0];
const organizationColorPalettes = organization?.organizationColorPalettes;
const onCreateCustomTheme = useMemoizedFn(async (theme: IColorTheme) => {
const currentThemeId = organization.organizationColorPalettes.selectedId;
if (!organization) return;
await updateOrganization({
organizationColorPalettes: {
selectedId: currentThemeId || theme.id,
palettes: [...organization.organizationColorPalettes.palettes, theme]
selectedId: theme.id,
palettes: [theme, ...organization.organizationColorPalettes.palettes]
}
});
});
const onDeleteCustomTheme = useMemoizedFn(async (themeId: string) => {
if (!organization) return;
const currentThemeId = organization.organizationColorPalettes.selectedId;
const isSelectedTheme = currentThemeId === themeId;
const firstTheme = organization.organizationColorPalettes.palettes[0];
await updateOrganization({
organizationColorPalettes: {
selectedId: isSelectedTheme ? firstTheme.id : currentThemeId,
selectedId: isSelectedTheme ? null : currentThemeId,
palettes: organization.organizationColorPalettes.palettes.filter(
(theme) => theme.id !== themeId
)
@ -41,6 +44,8 @@ export const DefaultThemeSelector = React.memo(() => {
});
const onModifyCustomTheme = useMemoizedFn(async (themeId: string, theme: IColorTheme) => {
if (!organization) return;
await updateOrganization({
organizationColorPalettes: {
selectedId: organization.organizationColorPalettes.selectedId,
@ -52,6 +57,8 @@ export const DefaultThemeSelector = React.memo(() => {
});
const onSelectTheme = useMemoizedFn((theme: IColorTheme) => {
if (!organization) return;
updateOrganization({
organizationColorPalettes: {
selectedId: theme.id,
@ -60,7 +67,8 @@ export const DefaultThemeSelector = React.memo(() => {
});
});
const organizationColorPalettes = organization?.organizationColorPalettes;
if (!organizationColorPalettes || !organization) return null;
if (!isFetchedThemes) return <CircleSpinnerLoader />;
@ -82,8 +90,10 @@ export const DefaultThemeSelector = React.memo(() => {
onDeleteCustomTheme={onDeleteCustomTheme}
onModifyCustomTheme={onModifyCustomTheme}
onChangeTheme={onSelectTheme}
themeListClassName={themeListClassName}
/>
);
});
}
);
DefaultThemeSelector.displayName = 'DefaultThemeSelector';

View File

@ -15,7 +15,6 @@ const meta: Meta<typeof DefaultThemeSelectorBase> = {
onDeleteCustomTheme: fn(),
onModifyCustomTheme: fn(),
selectedThemeId: 'custom-sunset',
useDefaultThemes: true,
customThemes: [
{
name: 'Custom Sunset',

View File

@ -3,7 +3,6 @@ import type { IColorTheme } from '../ThemeList/interfaces';
import { ThemeList } from '../ThemeList';
import { cn } from '@/lib/utils';
import { AddCustomThemeBase } from './AddCustomThemeBase';
import { useColorThemes } from '../../../../api/buster_rest/dictionaries';
export interface DefaultThemeSelectorProps {
customThemes: Omit<IColorTheme, 'selected'>[];
@ -13,31 +12,29 @@ export interface DefaultThemeSelectorProps {
onDeleteCustomTheme: (themeId: string) => Promise<void>;
onModifyCustomTheme: (themeId: string, theme: IColorTheme) => Promise<void>;
selectedThemeId: string | null;
useDefaultThemes?: boolean;
themeListClassName?: string;
className?: string;
}
export const DefaultThemeSelectorBase = React.memo(
({
customThemes,
themes,
useDefaultThemes = true,
selectedThemeId,
onChangeTheme,
themeListClassName,
className,
onCreateCustomTheme,
onDeleteCustomTheme,
onModifyCustomTheme
}: DefaultThemeSelectorProps) => {
const iThemes: Required<IColorTheme>[] = themes?.map((theme) => ({
...theme,
selected: theme.id === selectedThemeId,
id: theme.name
selected: theme.id === selectedThemeId
}));
return (
<div className="flex w-full flex-col space-y-2.5">
<div>
<div className={cn('flex w-full flex-col space-y-2.5', className)}>
<AddCustomThemeBase
customThemes={customThemes}
selectedThemeId={selectedThemeId}
@ -46,9 +43,13 @@ export const DefaultThemeSelectorBase = React.memo(
deleteCustomTheme={onDeleteCustomTheme}
modifyCustomTheme={onModifyCustomTheme}
/>
</div>
<div className={cn(themeListClassName)}>
<ThemeList themes={iThemes} onChangeColorTheme={onChangeTheme} />
<div>
<ThemeList
themes={iThemes}
onChangeColorTheme={onChangeTheme}
className={cn(themeListClassName)}
/>
</div>
</div>
);

View File

@ -3,7 +3,7 @@ import { AppTooltip } from '@/components/ui/tooltip';
import { ColorPicker } from '@/components/ui/color-picker';
import { cn } from '@/lib/utils';
import { useMemoizedFn } from '@/hooks';
import React, { useRef, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import {
DndContext,
DragEndEvent,
@ -166,13 +166,21 @@ const ColorWithPicker: React.FC<{
const originalColor = useRef(colorProp);
const [color, setColor] = useState(colorProp);
useEffect(() => {
setColor(colorProp);
originalColor.current = colorProp;
}, [colorProp]);
return (
<ColorPicker
value={color}
onChange={setColor}
align="center"
side="bottom"
onOpenChange={onPickerOpenChange}
onOpenChange={(v) => {
setColor(originalColor.current);
onPickerOpenChange(v);
}}
popoverChildren={
<div className="flex w-full items-center gap-2 border-t py-2">
<Button block variant={'default'} onClick={() => setColor(originalColor.current)}>

View File

@ -14,10 +14,11 @@ interface NewThemePopupProps {
selectedTheme?: IColorTheme;
onSave: (theme: IColorTheme) => Promise<void>;
onDelete?: (id: string) => Promise<void>;
onUpdate?: (theme: IColorTheme) => Promise<void>;
}
export const NewThemePopup = React.memo(
({ selectedTheme, onDelete, onSave }: NewThemePopupProps) => {
({ selectedTheme, onDelete, onUpdate, onSave }: NewThemePopupProps) => {
const [title, setTitle] = useState('');
const [colors, setColors] = useState<string[]>(DEFAULT_CHART_THEME);
const [id, setId] = useState(uuidv4());
@ -45,6 +46,13 @@ export const NewThemePopup = React.memo(
}, 350);
});
const onUpdateClick = useMemoizedFn(async () => {
await onUpdate?.({ id, name: title, colors });
setTimeout(() => {
reset();
}, 350);
});
useEffect(() => {
if (selectedTheme) {
setTitle(selectedTheme.name);
@ -54,7 +62,7 @@ export const NewThemePopup = React.memo(
}, [selectedTheme]);
return (
<div className="w-[280px]">
<div className="w-[280px] max-w-[340px]">
<div className="grid grid-cols-[80px_1fr] items-center gap-2 p-2.5">
<Text>Title</Text>
<Input
@ -67,13 +75,22 @@ export const NewThemePopup = React.memo(
</div>
<div className="w-full border-t"></div>
<div className="p-2.5">
<div className="flex space-x-1.5 p-2.5">
{onDelete && !isNewTheme && (
<Button
block
disabled={disableCreateTheme}
onClick={isNewTheme ? onSaveClick : onDeleteClick}
prefix={isNewTheme ? <Plus /> : <Trash />}>
{isNewTheme ? 'Create theme' : 'Delete theme'}
prefix={<Trash />}>
{'Delete theme'}
</Button>
)}
<Button
block
disabled={disableCreateTheme}
onClick={isNewTheme ? onSaveClick : onUpdateClick}
prefix={<Plus />}>
{isNewTheme || !onUpdate ? 'Create theme' : 'Update theme'}
</Button>
</div>
</div>

View File

@ -1 +1 @@
export * from './DefaultThemeSelectorBase';
export * from './DefaultThemeSelector';

View File

@ -2,10 +2,10 @@ import type React from 'react';
import { cn } from '@/lib/classMerge';
export const ThemeColorDots: React.FC<{
selected: boolean;
selected?: boolean;
colors: string[];
numberOfColors?: number | 'all';
}> = ({ selected, colors, numberOfColors = 'all' }) => {
}> = ({ selected = false, colors, numberOfColors = 'all' }) => {
const numberOfColorsToShow = numberOfColors === 'all' ? colors.length : numberOfColors;
return (
@ -14,8 +14,7 @@ export const ThemeColorDots: React.FC<{
<div
key={color + colorIdx}
className={cn(
'ball rounded-full',
colorIdx > 0 && '-ml-0.5 h-2 w-2 shadow-[0_0_0_0.75px]',
'ball -ml-0.5 h-2 w-2 rounded-full shadow-[0_0_0_0.75px]',
!selected ? 'shadow-item-select' : 'shadow-background'
)}
style={{ backgroundColor: color }}

View File

@ -22,7 +22,7 @@ export const ThemeList: React.FC<{
)}>
{themes.map((theme) => (
<ColorOption
key={theme.name}
key={theme.id}
theme={theme}
selected={theme.selected}
onChangeColorTheme={onChangeColorTheme}
@ -52,7 +52,7 @@ const ColorOption: React.FC<{
data-selected={selected}
className={cn(
'flex w-full items-center justify-between space-x-2.5 overflow-hidden',
'h-7 cursor-pointer rounded-sm px-3 py-2',
'h-7 min-h-7 cursor-pointer rounded-sm px-3 py-2',
selected ? 'bg-background border' : 'bg-item-active hover:bg-nav-item-hover'
)}>
<Text truncate variant={selected ? 'default' : 'secondary'}>

View File

@ -1,5 +1,5 @@
import type { OrganizationColorPalette } from '@buster/server-shared/organization';
import type { ColorPalette } from '@buster/server-shared/organization';
export type IColorTheme = OrganizationColorPalette & {
export type IColorTheme = ColorPalette & {
selected?: boolean;
};

View File

@ -1,30 +1,30 @@
'use client';
import React from 'react';
import React, { useMemo } from 'react';
import { SettingsCards } from './SettingsCard';
import { Text } from '@/components/ui/typography';
import { useGetMyUserInfo } from '@/api/buster_rest/users/queryRequests';
import { useMount } from '../../../hooks';
import { prefetchColorThemes, useColorThemes } from '../../../api/buster_rest/dictionaries';
import { ThemeColorDots } from '../colors/ThemeList/ThemeColorDots';
import { Popover } from '../../ui/popover';
import { DefaultThemeSelector } from '../colors/DefaultThemeSelector';
import { ChevronDown } from '../../ui/icons';
export const DefaultColorThemeCard = React.memo(() => {
const { data: userData } = useGetMyUserInfo();
const organization = userData?.organizations?.[0]!;
const defaultColorTheme = organization.organizationColorPalettes?.selectedId;
return (
<SettingsCards
cards={[
{
sections: [
<div className="flex items-center justify-between space-x-4">
<div key="default-color-theme" className="flex items-center justify-between space-x-4">
<div className="flex flex-col space-y-0.5">
<Text>Default color theme</Text>
<Text variant="secondary" size={'xs'}>
Default color theme that Buster will use when creating charts
</Text>
</div>
<div>PICKER</div>
<PickButton />
</div>
]
}
@ -34,3 +34,55 @@ export const DefaultColorThemeCard = React.memo(() => {
});
DefaultColorThemeCard.displayName = 'DefaultColorThemeCard';
const PickButton = React.memo(() => {
const { data: userData } = useGetMyUserInfo();
const { data: colorThemes } = useColorThemes();
const organization = userData?.organizations?.[0];
const customThemes = organization?.organizationColorPalettes.palettes ?? [];
const defaultColorThemeId = organization?.organizationColorPalettes.selectedId;
const allThemes = useMemo(() => {
return [...colorThemes, ...customThemes];
}, [colorThemes, customThemes]);
const defaultColorTheme = useMemo(() => {
return allThemes.find((theme) => theme.id === defaultColorThemeId);
}, [allThemes, defaultColorThemeId]);
const hasDefaultColorTheme = !!defaultColorTheme;
useMount(() => {
prefetchColorThemes();
});
return (
<Popover
className="p-0"
align="end"
content={
<div className="max-w-[320px] overflow-y-auto p-2.5">
<DefaultThemeSelector themeListClassName="max-h-[320px] h-full overflow-y-auto" />
</div>
}>
<div className="hover:bg-item-hover flex h-7 min-h-7 cursor-pointer items-center space-x-1.5 overflow-hidden rounded border px-2 py-1 pl-2.5">
<div>
{hasDefaultColorTheme ? (
<ThemeColorDots colors={defaultColorTheme.colors} numberOfColors={'all'} />
) : (
<Text variant="secondary" size={'xs'}>
No default color theme
</Text>
)}
</div>
<div className="text-icon-color flex items-center justify-center">
<ChevronDown />
</div>
</div>
</Popover>
);
});
PickButton.displayName = 'PickButton';

View File

@ -12,6 +12,8 @@ const Popover = PopoverPrimitive.Root;
interface PopoverProps extends React.ComponentPropsWithoutRef<typeof Popover> {
trigger?: PopoverTriggerType;
children: React.ReactNode;
open?: boolean;
}
const PopoverRoot: React.FC<PopoverProps> = ({ children, trigger = 'click', ...props }) => {

View File

@ -27,6 +27,10 @@ export const MetricStylingApp: React.FC<{
const { data: chartConfig } = useGetMetric({ id: metricId }, { select: (x) => x.chart_config });
const { data: metricData } = useGetMetricData({ id: metricId }, { enabled: false });
useMount(() => {
prefetchColorThemes();
});
if (!chartConfig) return null;
const columnMetadata = metricData?.data_metadata?.column_metadata || [];
@ -92,9 +96,7 @@ export const MetricStylingApp: React.FC<{
barAndLineAxis
);
useMount(() => {
prefetchColorThemes();
});
return (
<div className="flex h-full w-full flex-col overflow-hidden pt-3">

View File

@ -23,7 +23,7 @@ const OrganizationColorPaletteSchema = z.object({
const UpdateOrganizationInputSchema = z.object({
organizationId: z.string().uuid('Organization ID must be a valid UUID'),
organizationColorPalettes: z.object({
selectedId: z.string().min(1).max(255),
selectedId: z.string().min(1).max(255).nullable(),
palettes: z.array(OrganizationColorPaletteSchema).refine(
(palettes) => {
if (!palettes || palettes.length === 0) return true;

View File

@ -1,3 +1,3 @@
import type { OrganizationColorPalette } from '../organization/organization.types';
import type { ColorPalette } from '../organization/organization.types';
export type ColorThemeDictionariesResponse = OrganizationColorPalette[];
export type ColorThemeDictionariesResponse = ColorPalette[];

View File

@ -11,12 +11,17 @@ const HexColorSchema = z
'Must be a valid 3 or 6 digit hex color code (e.g., #fff or #ffffff)'
);
export const OrganizationColorPaletteSchema = z.object({
export const ColorPalettesSchema = z.object({
id: z.string(),
colors: z.array(HexColorSchema).min(1).max(25),
name: z.string().min(1).max(255),
});
export const OrganizationColorPaletteSchema = z.object({
selectedId: z.string().nullable(),
palettes: z.array(ColorPalettesSchema),
});
export const OrganizationSchema = z.object({
id: z.string(),
name: z.string(),
@ -28,13 +33,11 @@ export const OrganizationSchema = z.object({
domains: z.array(z.string()).nullable(),
restrictNewUserInvitations: z.boolean(),
defaultRole: OrganizationRoleSchema,
organizationColorPalettes: z.object({
selectedId: z.string(),
palettes: z.array(OrganizationColorPaletteSchema),
}),
organizationColorPalettes: OrganizationColorPaletteSchema,
});
export type Organization = z.infer<typeof OrganizationSchema>;
export type OrganizationColorPalette = z.infer<typeof OrganizationColorPaletteSchema>;
export type ColorPalette = z.infer<typeof ColorPalettesSchema>;
type _OrganizationEqualityCheck = Expect<Equal<Organization, typeof organizations.$inferSelect>>;

View File

@ -3,10 +3,7 @@ import { OrganizationColorPaletteSchema } from './organization.types';
// Update Organization Request/Response Types
export const UpdateOrganizationRequestSchema = z.object({
organizationColorPalettes: z.object({
selectedId: z.string(),
palettes: z.array(OrganizationColorPaletteSchema),
}),
organizationColorPalettes: OrganizationColorPaletteSchema,
});
export type UpdateOrganizationRequest = z.infer<typeof UpdateOrganizationRequestSchema>;