Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4b9c52a
Add Settings page
fzaninotto Feb 11, 2026
e6403c9
Use Shadcn Admin Kit commponents in Settings Page
fzaninotto Feb 11, 2026
a206c32
Simplify config storage by using the Store
fzaninotto Feb 12, 2026
4e16a01
Remove contact gender from config
fzaninotto Feb 12, 2026
2ba24ad
Allow admins to upload custom logos
fzaninotto Feb 12, 2026
695c973
Fix bug on logout
fzaninotto Feb 12, 2026
6c25ccb
Fix liniting
fzaninotto Feb 12, 2026
0c06d83
Convert configuration values to key value
fzaninotto Feb 16, 2026
8a8fb81
Fix ts compilation
fzaninotto Feb 16, 2026
cc49b20
Fix prettier warning
fzaninotto Feb 18, 2026
284e327
Wrap handleCallback, too
fzaninotto Feb 18, 2026
9521fc0
Refactor to use Shadcn Admin Kit Primitives
fzaninotto Feb 18, 2026
e3453e5
Update registry
fzaninotto Feb 18, 2026
e73b20c
Fix logo alignment
fzaninotto Feb 18, 2026
fe335af
Fix linter issue
fzaninotto Feb 18, 2026
85862dd
Fix linter warnings
fzaninotto Feb 18, 2026
3418b7c
simplify UI
fzaninotto Feb 19, 2026
4a879b4
Validate orphan deals
fzaninotto Feb 19, 2026
8b4d208
Improve UI
fzaninotto Feb 19, 2026
7a0c237
MAke stored configuration forward compatible
fzaninotto Feb 19, 2026
75256cd
Rename components
fzaninotto Feb 19, 2026
5dda9a4
Merge migrations
fzaninotto Feb 19, 2026
7e00b16
Misc review
fzaninotto Feb 19, 2026
a92a0f8
Add documentation
fzaninotto Feb 19, 2026
0a941c9
Update settings sections
fzaninotto Feb 19, 2026
7d8b004
Add unit test for the validateItemsInUse test
fzaninotto Feb 19, 2026
b9ce9d7
Do not include test files in the registry
fzaninotto Feb 19, 2026
fcdd176
Fix prettier
fzaninotto Feb 19, 2026
ac9436a
fix prettier warning
fzaninotto Feb 20, 2026
6eb7679
Merge branch 'main' into user-config
fzaninotto Feb 20, 2026
137deb5
Fix prettier
fzaninotto Feb 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export default defineConfig({
label: "Users Documentation",
items: [
"users/user-management",
"users/settings",
"users/import-data",
"users/merging-contacts",
"users/inbound-email",
Expand Down
29 changes: 18 additions & 11 deletions doc/src/content/docs/developers/customizing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ title: Configuration
description: This document explains how to customize the Atomic CRM application.
---

Developers can customize the Atomic CRM application to suit their business needs. Some of the customizations can be achieved via configuration on the `<CRM>` component, while others require changes to the source code.
Admin users can already customize the application domain-specific data (e.g., deal stages, note statuses, etc.) via [the Settings page](../user//settings). However, developers can go further and define the defaults for these data by configuring the `<CRM>` component in `src/App.tsx`.

Other customizations require changes to the source code.

## The `<CRM>` component

Expand All @@ -26,12 +28,14 @@ import { CRM } from "@/components/atomic-crm/root/CRM";

const App = () => (
<CRM
contactGender={[
{ value: 'male', label: 'He' },
{ value: 'female', label: 'She' },
companySectors={[
{ value: 'technology', label: 'Technology' },
{ value: 'finance', label: 'Finance' },
]}
dealCategories={[
{ value: 'copywriting', label: 'Copywriting' },
{ value: 'design', label: 'Design' },
]}
companySectors={['Technology', 'Finance']}
dealCategories={['Copywriting', 'Design']}
dealPipelineStatuses={['won']}
dealStages={[
{ value: 'opportunity', label: 'Opportunity' },
Expand All @@ -44,7 +48,11 @@ const App = () => (
{ value: 'warm', label: 'Warm', color: '#e8cb7d' },
{ value: 'hot', label: 'Hot', color: '#e88b7d' },
]}
taskTypes={['Call', 'Email', 'Meeting']}
taskTypes={[
{ value: 'call', label: 'Call' },
{ value: 'email', label: 'Email' },
{ value: 'meeting', label: 'Meeting' },
]}
/>
);

Expand All @@ -55,16 +63,15 @@ export default App;

| Props | Description | Type |
|-------------------------|-----------------------------------------------------------------------|-----------------|
| `contactGender` | The gender options for contacts used in the application. | ContactGender[] |
| `companySectors` | The list of company sectors used in the application. | string[] |
| `companySectors` | The list of company sectors used in the application. | LabeledValue[] |
| `darkTheme` | The theme to use when the application is in dark mode. | RaThemeOptions |
| `dealCategories` | The categories of deals used in the application. | string[] |
| `dealCategories` | The categories of deals used in the application. | LabeledValue[] |
| `dealPipelineStatuses` | The statuses of deals in the pipeline used in the application | string[] |
| `dealStages` | The stages of deals used in the application. | DealStage[] |
| `lightTheme` | The theme to use when the application is in light mode. | RaThemeOptions |
| `logo` | The logo used in the CRM application. | string |
| `noteStatuses` | The statuses of notes used in the application. | NoteStatus[] |
| `taskTypes` | The types of tasks used in the application. | string[] |
| `taskTypes` | The types of tasks used in the application. | LabeledValue[] |
| `title` | The title of the CRM application. | string |

## Disabling Telemetry
Expand Down
12 changes: 10 additions & 2 deletions doc/src/content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,16 @@ import { CRM } from "@/components/atomic-crm/root/CRM";
const App = () => (
<CRM
title="Acme CRM"
taskTypes={['Call', 'Email', 'Meeting']}
dealCategories={['eCommerce', 'SaaS', 'Consulting']}
taskTypes={[
{ value: 'call', label: 'Call' },
{ value: 'email', label: 'Email' },
{ value: 'meeting', label: 'Meeting' },
]}
dealCategories={[
{ value: 'ecommerce', label: 'eCommerce' },
{ value: 'saas', label: 'SaaS' },
{ value: 'consulting', label: 'Consulting' },
]}
/>
);

Expand Down
29 changes: 29 additions & 0 deletions doc/src/content/docs/users/settings.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
title: Application Settings
description: This document explains how to configure Atomic CRM.
---

Admin users can configure various aspects of Atomic CRM to tailor the application to their specific needs. This includes customizing the user interface, defining categories, etc.

## Accessing the Settings Page

If you're logged in as an admin user, you can access the configuration page by clicking on your profile picture in the top right corner of the application and selecting "Settings" from the dropdown menu.

![Settings Page](../../images/settings.png)

The settings page is organized into different sections, each corresponding to a specific aspect of the application that can be configured. You can navigate between these sections using the left sidebar.

## Changing Application Settings

The Settings page lets you customize:

- The application name and logo
- The company sectors
- The stages of the deal pipeline (e.g., prospecting, negotiation, closed-won, etc.)
- The deal categories
- The note statuses (hot, cold, etc.)
- The task types (call, email, etc.)

Once you save your changes, they will be immediately reflected across the application. For example, if you update the deal stages, the new stages will be available for users to drag and drop deals in the Kanban view, and they will also be available when creating or editing a deal.

When updating the name of an existing category (e.g., a deal stage), the new name will be applied to all existing records that belong to that category. For instance, if you rename the "Prospecting" deal stage to "Lead", all deals that were previously in the "Prospecting" stage will now be in the "Lead" stage.
Binary file added doc/src/content/images/settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@
"path": "src/components/atomic-crm/settings/SettingsPage.tsx",
"type": "registry:component"
},
{
"path": "src/components/atomic-crm/settings/ProfilePage.tsx",
"type": "registry:component"
},
{
"path": "src/components/atomic-crm/sales/index.ts",
"type": "registry:component"
Expand All @@ -147,6 +151,10 @@
"path": "src/components/atomic-crm/sales/SaleName.tsx",
"type": "registry:component"
},
{
"path": "src/components/atomic-crm/root/useConfigurationLoader.ts",
"type": "registry:component"
},
{
"path": "src/components/atomic-crm/root/i18nProvider.tsx",
"type": "registry:component"
Expand Down Expand Up @@ -627,6 +635,10 @@
"path": "src/components/atomic-crm/contacts/exportToVCard.ts",
"type": "registry:component"
},
{
"path": "src/components/atomic-crm/contacts/contactGender.ts",
"type": "registry:component"
},
{
"path": "src/components/atomic-crm/contacts/TagsListEdit.tsx",
"type": "registry:component"
Expand Down
1 change: 0 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { CRM } from "@/components/atomic-crm/root/CRM";
* Application entry point
*
* Customize Atomic CRM by passing props to the CRM component:
* - contactGender
* - companySectors
* - darkTheme
* - dealCategories
Expand Down
12 changes: 7 additions & 5 deletions src/components/atomic-crm/companies/CompanyAside.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { UrlField } from "@/components/admin/url-field";
import { SelectField } from "@/components/admin/select-field";

import { AsideSection } from "../misc/AsideSection";
import { useConfigurationContext } from "../root/ConfigurationContext";
import { SaleName } from "../sales/SaleName";
import type { Company } from "../types";
import { sizes } from "./sizes";
Expand Down Expand Up @@ -97,17 +98,18 @@ export const CompanyInfo = ({ record }: { record: Company }) => {
};

export const ContextInfo = ({ record }: { record: Company }) => {
const { companySectors } = useConfigurationContext();
if (!record.revenue && !record.id) {
return null;
}

const sectorLabel = companySectors.find(
(s) => s.value === record.sector,
)?.label;

return (
<AsideSection title="Context">
{record.sector && (
<span>
Sector: <TextField source="sector" />
</span>
)}
{sectorLabel && <span>Sector: {sectorLabel}</span>}
{record.size && (
<span>
Size: <SelectField source="size" choices={sizes} />
Expand Down
8 changes: 7 additions & 1 deletion src/components/atomic-crm/companies/CompanyCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@ import { ReferenceManyField } from "@/components/admin/reference-many-field";
import { Card } from "@/components/ui/card";

import { Avatar as ContactAvatar } from "../contacts/Avatar";
import { useConfigurationContext } from "../root/ConfigurationContext";
import type { Company } from "../types";
import { CompanyAvatar } from "./CompanyAvatar";

export const CompanyCard = (props: { record?: Company }) => {
const createPath = useCreatePath();
const record = useRecordContext<Company>(props);
const { companySectors } = useConfigurationContext();
if (!record) return null;

const sectorLabel = companySectors.find(
(s) => s.value === record.sector,
)?.label;

return (
<Link
to={createPath({
Expand All @@ -27,7 +33,7 @@ export const CompanyCard = (props: { record?: Company }) => {
<CompanyAvatar />
<div className="text-center mt-1">
<h6 className="text-sm font-medium">{record.name}</h6>
<p className="text-xs text-muted-foreground">{record.sector}</p>
<p className="text-xs text-muted-foreground">{sectorLabel}</p>
</div>
</div>
<div className="flex flex-row w-full justify-between gap-2">
Expand Down
7 changes: 3 additions & 4 deletions src/components/atomic-crm/companies/CompanyInputs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,9 @@ const CompanyContextInputs = () => {
<h6 className="text-lg font-semibold">Context</h6>
<SelectInput
source="sector"
choices={companySectors.map((sector) => ({
id: sector,
name: sector,
}))}
choices={companySectors}
optionText="label"
optionValue="value"
helperText={false}
/>
<SelectInput source="size" choices={sizes} helperText={false} />
Expand Down
12 changes: 4 additions & 8 deletions src/components/atomic-crm/companies/CompanyListFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ import { sizes } from "./sizes";
export const CompanyListFilter = () => {
const { identity } = useGetIdentity();
const { companySectors } = useConfigurationContext();
const sectors = companySectors.map((sector) => ({
id: sector,
name: sector,
}));
return (
<div className="w-52 min-w-52 flex flex-col gap-8">
<FilterLiveForm>
Expand All @@ -32,12 +28,12 @@ export const CompanyListFilter = () => {
</FilterCategory>

<FilterCategory icon={<Truck className="h-4 w-4" />} label="Sector">
{sectors.map((sector) => (
{companySectors.map((sector) => (
<ToggleFilterButton
className="w-full justify-between"
label={sector.name}
key={sector.name}
value={{ sector: sector.id }}
label={sector.label}
key={sector.value}
value={{ sector: sector.value }}
/>
))}
</FilterCategory>
Expand Down
6 changes: 4 additions & 2 deletions src/components/atomic-crm/companies/CompanyShow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ const CreateRelatedContactButton = () => {

const DealsIterator = () => {
const { data: deals, error, isPending } = useListContext<Deal>();
const { dealStages } = useConfigurationContext();
const { dealStages, dealCategories } = useConfigurationContext();
if (isPending || error) return null;

const now = Date.now();
Expand All @@ -272,7 +272,9 @@ const DealsIterator = () => {
currencyDisplay: "narrowSymbol",
minimumSignificantDigits: 3,
})}
{deal.category ? `, ${deal.category}` : ""}
{deal.category
? `, ${dealCategories.find((c) => c.value === deal.category)?.label ?? deal.category}`
: ""}
</div>
</div>
<div className="text-right">
Expand Down
3 changes: 1 addition & 2 deletions src/components/atomic-crm/contacts/ContactInputs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ArrayInput } from "@/components/admin/array-input";
import { SimpleFormIterator } from "@/components/admin/simple-form-iterator";

import { isLinkedinUrl } from "../misc/isLinkedInUrl";
import { useConfigurationContext } from "../root/ConfigurationContext";
import { contactGender } from "./contactGender";
import type { Sale } from "../types";
import { Avatar } from "./Avatar";
import { AutocompleteCompanyInput } from "../companies/AutocompleteCompanyInput.tsx";
Expand Down Expand Up @@ -43,7 +43,6 @@ export const ContactInputs = () => {
};

const ContactIdentityInputs = () => {
const { contactGender } = useConfigurationContext();
return (
<div className="flex flex-col gap-4">
<h6 className="text-lg font-semibold">Identity</h6>
Expand Down
3 changes: 1 addition & 2 deletions src/components/atomic-crm/contacts/ContactPersonalInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import { TextField } from "@/components/admin/text-field";
import { EmailField } from "@/components/admin/email-field";
import { Mail, Phone, Linkedin } from "lucide-react";
import type { ReactNode } from "react";
import { useConfigurationContext } from "../root/ConfigurationContext";
import { contactGender } from "./contactGender";
import type { Contact } from "../types";

export const ContactPersonalInfo = () => {
const { contactGender } = useConfigurationContext();
const record = useRecordContext<Contact>();

if (!record) return null;
Expand Down
9 changes: 9 additions & 0 deletions src/components/atomic-crm/contacts/contactGender.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Mars, NonBinary, Venus } from "lucide-react";

import type { ContactGender } from "../types";

export const contactGender: ContactGender[] = [
{ value: "male", label: "He/Him", icon: Mars },
{ value: "female", label: "She/Her", icon: Venus },
{ value: "nonbinary", label: "They/Them", icon: NonBinary },
];
7 changes: 6 additions & 1 deletion src/components/atomic-crm/deals/DealCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ReferenceField } from "@/components/admin/reference-field";
import { Card, CardContent } from "@/components/ui/card";

import { CompanyAvatar } from "../companies/CompanyAvatar";
import { useConfigurationContext } from "../root/ConfigurationContext";
import type { Deal } from "../types";

export const DealCard = ({ deal, index }: { deal: Deal; index: number }) => {
Expand All @@ -27,7 +28,11 @@ export const DealCardContent = ({
snapshot?: any;
deal: Deal;
}) => {
const { dealCategories } = useConfigurationContext();
const redirect = useRedirect();
const categoryLabel = dealCategories.find(
(c) => c.value === deal.category,
)?.label;
const handleClick = () => {
redirect(`/deals/${deal.id}/show`, undefined, undefined, undefined, {
_scrollToTop: false,
Expand Down Expand Up @@ -68,7 +73,7 @@ export const DealCardContent = ({
currencyDisplay: "narrowSymbol",
minimumSignificantDigits: 3,
})}
{deal.category ? `, ${deal.category}` : ""}
{categoryLabel ? `, ${categoryLabel}` : ""}
</p>
</div>
</CardContent>
Expand Down
14 changes: 6 additions & 8 deletions src/components/atomic-crm/deals/DealInputs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,9 @@ const DealMiscInputs = () => {
<SelectInput
source="category"
label="Category"
choices={dealCategories.map((type) => ({
id: type,
name: type,
}))}
choices={dealCategories}
optionText="label"
optionValue="value"
helperText={false}
/>
<NumberInput
Expand All @@ -90,10 +89,9 @@ const DealMiscInputs = () => {
/>
<SelectInput
source="stage"
choices={dealStages.map((stage) => ({
id: stage.value,
name: stage.label,
}))}
choices={dealStages}
optionText="label"
optionValue="value"
defaultValue="opportunity"
helperText={false}
validate={required()}
Expand Down
5 changes: 3 additions & 2 deletions src/components/atomic-crm/deals/DealList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ const DealList = () => {
<SelectInput
source="category"
emptyText="Category"
label={false}
choices={dealCategories.map((type) => ({ id: type, name: type }))}
choices={dealCategories}
optionText="label"
optionValue="value"
/>,
<OnlyMineInput source="sales_id" alwaysOn />,
];
Expand Down
Loading