snapshot of https://github.com/marmelab/atomic-crm.git (main, 2026-09-18; blobs > 131072B omitted)
Some checks failed
✅ Check / 🔬 ESLint (push) Failing after 21s
✅ Check / 🏷️ Typecheck (push) Failing after 11s
✅ Check / 🔎 Test (push) Failing after 11s
✅ Check / e2e-test (push) Failing after 11s
✅ Check / 🔨 Build (push) Failing after 11s
🚀 Deploy / 🚀 Deploy (doc) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (demo) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (supabase) (push) Failing after 18s
21
doc/.gitignore
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# build output
|
||||
dist/
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
49
doc/README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Starlight Starter Kit: Basics
|
||||
|
||||
[](https://starlight.astro.build)
|
||||
|
||||
```
|
||||
npm create astro@latest -- --template starlight
|
||||
```
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
|
||||
## 🚀 Project Structure
|
||||
|
||||
Inside of your Astro + Starlight project, you'll see the following folders and files:
|
||||
|
||||
```
|
||||
.
|
||||
├── public/
|
||||
├── src/
|
||||
│ ├── assets/
|
||||
│ ├── content/
|
||||
│ │ └── docs/
|
||||
│ └── content.config.ts
|
||||
├── astro.config.mjs
|
||||
├── package.json
|
||||
└── tsconfig.json
|
||||
```
|
||||
|
||||
Starlight looks for `.md` or `.mdx` files in the `src/content/docs/` directory. Each file is exposed as a route based on its file name.
|
||||
|
||||
Images can be added to `src/assets/` and embedded in Markdown with a relative link.
|
||||
|
||||
Static assets, like favicons, can be placed in the `public/` directory.
|
||||
|
||||
## 🧞 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :------------------------ | :----------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:4321` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
||||
|
||||
## 👀 Want to learn more?
|
||||
|
||||
Check out [Starlight’s docs](https://starlight.astro.build/), read [the Astro documentation](https://docs.astro.build), or jump into the [Astro Discord server](https://astro.build/chat).
|
||||
109
doc/astro.config.mjs
Normal file
@@ -0,0 +1,109 @@
|
||||
// @ts-check
|
||||
import { defineConfig } from "astro/config";
|
||||
import starlight from "@astrojs/starlight";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
base: "/atomic-crm/doc/",
|
||||
vite: {
|
||||
plugins: [tailwindcss()],
|
||||
},
|
||||
build: {
|
||||
assets: "assets",
|
||||
},
|
||||
integrations: [
|
||||
starlight({
|
||||
title: "Atomic CRM",
|
||||
favicon: "./favicon.svg",
|
||||
customCss: ["./src/styles/global.css"],
|
||||
logo: {
|
||||
dark: "./public/logo_atomic_crm_dark.svg",
|
||||
light: "./public/logo_atomic_crm_light.svg",
|
||||
},
|
||||
head: [
|
||||
{
|
||||
tag: "meta",
|
||||
attrs: {
|
||||
property: "og:title",
|
||||
content: "Atomic CRM Documentation",
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: "meta",
|
||||
attrs: {
|
||||
property: "og:description",
|
||||
content: "A full-featured CRM toolkit for personalized solutions.",
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: "meta",
|
||||
attrs: {
|
||||
property: "og:type",
|
||||
content: "website",
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: "meta",
|
||||
attrs: {
|
||||
property: "og:url",
|
||||
content: "https://marmelab.com/atomic-crm/doc",
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: "meta",
|
||||
attrs: {
|
||||
property: "og:image",
|
||||
content:
|
||||
"https://marmelab.com/atomic-crm/img/atomic-crm-banner.png",
|
||||
},
|
||||
},
|
||||
// add Umami analytics script tag.
|
||||
{
|
||||
tag: "script",
|
||||
attrs: {
|
||||
src: "https://gursikso.marmelab.com/script.js",
|
||||
"data-website-id": "1dc1c802-5494-4c69-b507-3f2eff25091f",
|
||||
defer: true,
|
||||
async: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
social: [
|
||||
{
|
||||
icon: "github",
|
||||
label: "GitHub",
|
||||
href: "https://github.com/marmelab/atomic-crm",
|
||||
},
|
||||
],
|
||||
sidebar: [
|
||||
{
|
||||
label: "Getting Started",
|
||||
link: "/",
|
||||
},
|
||||
{
|
||||
label: "Users Documentation",
|
||||
items: [
|
||||
"users/user-management",
|
||||
"users/preferences",
|
||||
"users/settings",
|
||||
"users/import-data",
|
||||
"users/merging-contacts",
|
||||
"users/inbound-email",
|
||||
"users/mobile-app",
|
||||
"users/mcp-server",
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Developers Documentation",
|
||||
autogenerate: { directory: "developers" },
|
||||
},
|
||||
{
|
||||
label: "What's New",
|
||||
link: "https://github.com/marmelab/atomic-crm/releases",
|
||||
attrs: { target: "_blank" },
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
23
doc/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "atomic-crm-doc",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/starlight": "^0.38.3",
|
||||
"@astrojs/starlight-tailwind": "^4.0.1",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"astro": "^6.4.4",
|
||||
"sharp": "^0.34.2",
|
||||
"tailwindcss": "^4.1.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gh-pages": "^6.3.0"
|
||||
}
|
||||
}
|
||||
BIN
doc/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
14
doc/public/favicon.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_112_17)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M67.9739 68.5032C74.278 288.546 97.2991 438.258 108.22 486.25L81.7558 492.273C69.8751 440.063 45.2902 278.111 40.1589 42.0743L41.5181 42.0447L41.5476 40.6883C277.584 45.8196 439.536 70.4046 491.746 82.2852L485.724 108.75C437.731 97.8285 288.018 74.8073 67.9739 68.5032Z" fill="black"/>
|
||||
<path d="M67.9739 68.5032L69.0102 32.3302L30.7033 31.2327L31.8008 69.5396L67.9739 68.5032ZM108.22 486.25L116.25 521.536L151.535 513.507L143.506 478.221L108.22 486.25ZM81.7558 492.273L46.4699 500.302L54.4995 535.588L89.7853 527.558L81.7558 492.273ZM40.1589 42.0743L39.3726 5.89492L3.19305 6.68125L3.97957 42.8608L40.1589 42.0743ZM41.5181 42.0447L42.3045 78.224L76.9443 77.4712L77.6975 42.8314L41.5181 42.0447ZM41.5476 40.6883L42.3341 4.50902L6.15501 3.72252L5.36832 39.9017L41.5476 40.6883ZM491.746 82.2852L527.032 90.3148L535.061 55.0289L499.776 46.9994L491.746 82.2852ZM485.724 108.75L477.694 144.035L512.98 152.065L521.01 116.779L485.724 108.75ZM31.8008 69.5396C38.1498 291.145 61.3402 443.33 72.9343 494.28L143.506 478.221C133.258 433.186 110.406 285.947 104.147 67.4669L31.8008 69.5396ZM100.191 450.965L73.7262 456.987L89.7853 527.558L116.25 521.536L100.191 450.965ZM117.042 484.243C105.852 435.07 81.4335 275.67 76.3382 41.2877L3.97957 42.8608C9.1468 280.552 33.8982 445.056 46.4699 500.302L117.042 484.243ZM40.9452 78.2536L42.3045 78.224L40.7318 5.86538L39.3726 5.89492L40.9452 78.2536ZM5.36832 39.9017L5.33882 41.258L77.6975 42.8314L77.727 41.475L5.36832 39.9017ZM499.776 46.9994C444.529 34.4277 280.025 9.67622 42.3341 4.50902L40.7611 76.8677C275.143 81.9629 434.544 106.381 483.716 117.571L499.776 46.9994ZM521.01 116.779L527.032 90.3148L456.46 74.2556L450.438 100.72L521.01 116.779ZM66.9375 104.676C285.419 110.936 432.659 133.787 477.694 144.035L493.753 73.4637C442.803 61.8696 290.617 38.679 69.0102 32.3302L66.9375 104.676Z" fill="black"/>
|
||||
<path d="M422.589 501.244C411.288 508.494 404.495 537.463 357.466 501.244C314.818 468.399 315.989 399.344 350.213 365.119C405.869 309.463 500.443 290.457 584.885 374.9C669.328 459.343 672.846 572.253 584.885 660.214C496.924 748.175 321.002 796.154 185.062 660.214C49.1221 524.274 80.5161 338.98 198.224 221.272C315.932 103.565 510.413 40.5004 698.766 180.647C724.59 199.861 724.563 199.888 742.594 220.748" stroke="black" stroke-width="99.5167"/>
|
||||
<path d="M441.014 458.87C459.308 477.164 459.254 506.521 441.334 524.441C423.414 542.361 394.057 542.416 375.763 524.121C357.468 505.827 357.523 476.47 375.443 458.55C393.363 440.63 422.719 440.576 441.014 458.87Z" fill="black" stroke="black" stroke-width="45.2348"/>
|
||||
<path d="M741.489 291.533C711.213 260.04 671.51 214.18 643.153 198.672C675.899 179.381 750.221 152.218 779.899 187.565C816.996 231.75 774.29 325.653 741.489 291.533Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_112_17">
|
||||
<rect width="800" height="800" fill="black"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
14
doc/public/logo_atomic_crm_dark.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_112_17)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M67.9739 68.5032C74.278 288.546 97.2991 438.258 108.22 486.25L81.7558 492.273C69.8751 440.063 45.2902 278.111 40.1589 42.0743L41.5181 42.0447L41.5476 40.6883C277.584 45.8196 439.536 70.4046 491.746 82.2852L485.724 108.75C437.731 97.8285 288.018 74.8073 67.9739 68.5032Z" fill="white"/>
|
||||
<path d="M67.9739 68.5032L69.0102 32.3302L30.7033 31.2327L31.8008 69.5396L67.9739 68.5032ZM108.22 486.25L116.25 521.536L151.535 513.507L143.506 478.221L108.22 486.25ZM81.7558 492.273L46.4699 500.302L54.4995 535.588L89.7853 527.558L81.7558 492.273ZM40.1589 42.0743L39.3726 5.89492L3.19305 6.68125L3.97957 42.8608L40.1589 42.0743ZM41.5181 42.0447L42.3045 78.224L76.9443 77.4712L77.6975 42.8314L41.5181 42.0447ZM41.5476 40.6883L42.3341 4.50902L6.15501 3.72252L5.36832 39.9017L41.5476 40.6883ZM491.746 82.2852L527.032 90.3148L535.061 55.0289L499.776 46.9994L491.746 82.2852ZM485.724 108.75L477.694 144.035L512.98 152.065L521.01 116.779L485.724 108.75ZM31.8008 69.5396C38.1498 291.145 61.3402 443.33 72.9343 494.28L143.506 478.221C133.258 433.186 110.406 285.947 104.147 67.4669L31.8008 69.5396ZM100.191 450.965L73.7262 456.987L89.7853 527.558L116.25 521.536L100.191 450.965ZM117.042 484.243C105.852 435.07 81.4335 275.67 76.3382 41.2877L3.97957 42.8608C9.1468 280.552 33.8982 445.056 46.4699 500.302L117.042 484.243ZM40.9452 78.2536L42.3045 78.224L40.7318 5.86538L39.3726 5.89492L40.9452 78.2536ZM5.36832 39.9017L5.33882 41.258L77.6975 42.8314L77.727 41.475L5.36832 39.9017ZM499.776 46.9994C444.529 34.4277 280.025 9.67622 42.3341 4.50902L40.7611 76.8677C275.143 81.9629 434.544 106.381 483.716 117.571L499.776 46.9994ZM521.01 116.779L527.032 90.3148L456.46 74.2556L450.438 100.72L521.01 116.779ZM66.9375 104.676C285.419 110.936 432.659 133.787 477.694 144.035L493.753 73.4637C442.803 61.8696 290.617 38.679 69.0102 32.3302L66.9375 104.676Z" fill="white"/>
|
||||
<path d="M422.589 501.244C411.288 508.494 404.495 537.463 357.466 501.244C314.818 468.399 315.989 399.344 350.213 365.119C405.869 309.463 500.443 290.457 584.885 374.9C669.328 459.343 672.846 572.253 584.885 660.214C496.924 748.175 321.002 796.154 185.062 660.214C49.1221 524.274 80.5161 338.98 198.224 221.272C315.932 103.565 510.413 40.5004 698.766 180.647C724.59 199.861 724.563 199.888 742.594 220.748" stroke="white" stroke-width="99.5167"/>
|
||||
<path d="M441.014 458.87C459.308 477.164 459.254 506.521 441.334 524.441C423.414 542.361 394.057 542.416 375.763 524.121C357.468 505.827 357.523 476.47 375.443 458.55C393.363 440.63 422.719 440.576 441.014 458.87Z" fill="white" stroke="white" stroke-width="45.2348"/>
|
||||
<path d="M741.489 291.533C711.213 260.04 671.51 214.18 643.153 198.672C675.899 179.381 750.221 152.218 779.899 187.565C816.996 231.75 774.29 325.653 741.489 291.533Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_112_17">
|
||||
<rect width="800" height="800" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
14
doc/public/logo_atomic_crm_light.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_112_17)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M67.9739 68.5032C74.278 288.546 97.2991 438.258 108.22 486.25L81.7558 492.273C69.8751 440.063 45.2902 278.111 40.1589 42.0743L41.5181 42.0447L41.5476 40.6883C277.584 45.8196 439.536 70.4046 491.746 82.2852L485.724 108.75C437.731 97.8285 288.018 74.8073 67.9739 68.5032Z" fill="black"/>
|
||||
<path d="M67.9739 68.5032L69.0102 32.3302L30.7033 31.2327L31.8008 69.5396L67.9739 68.5032ZM108.22 486.25L116.25 521.536L151.535 513.507L143.506 478.221L108.22 486.25ZM81.7558 492.273L46.4699 500.302L54.4995 535.588L89.7853 527.558L81.7558 492.273ZM40.1589 42.0743L39.3726 5.89492L3.19305 6.68125L3.97957 42.8608L40.1589 42.0743ZM41.5181 42.0447L42.3045 78.224L76.9443 77.4712L77.6975 42.8314L41.5181 42.0447ZM41.5476 40.6883L42.3341 4.50902L6.15501 3.72252L5.36832 39.9017L41.5476 40.6883ZM491.746 82.2852L527.032 90.3148L535.061 55.0289L499.776 46.9994L491.746 82.2852ZM485.724 108.75L477.694 144.035L512.98 152.065L521.01 116.779L485.724 108.75ZM31.8008 69.5396C38.1498 291.145 61.3402 443.33 72.9343 494.28L143.506 478.221C133.258 433.186 110.406 285.947 104.147 67.4669L31.8008 69.5396ZM100.191 450.965L73.7262 456.987L89.7853 527.558L116.25 521.536L100.191 450.965ZM117.042 484.243C105.852 435.07 81.4335 275.67 76.3382 41.2877L3.97957 42.8608C9.1468 280.552 33.8982 445.056 46.4699 500.302L117.042 484.243ZM40.9452 78.2536L42.3045 78.224L40.7318 5.86538L39.3726 5.89492L40.9452 78.2536ZM5.36832 39.9017L5.33882 41.258L77.6975 42.8314L77.727 41.475L5.36832 39.9017ZM499.776 46.9994C444.529 34.4277 280.025 9.67622 42.3341 4.50902L40.7611 76.8677C275.143 81.9629 434.544 106.381 483.716 117.571L499.776 46.9994ZM521.01 116.779L527.032 90.3148L456.46 74.2556L450.438 100.72L521.01 116.779ZM66.9375 104.676C285.419 110.936 432.659 133.787 477.694 144.035L493.753 73.4637C442.803 61.8696 290.617 38.679 69.0102 32.3302L66.9375 104.676Z" fill="black"/>
|
||||
<path d="M422.589 501.244C411.288 508.494 404.495 537.463 357.466 501.244C314.818 468.399 315.989 399.344 350.213 365.119C405.869 309.463 500.443 290.457 584.885 374.9C669.328 459.343 672.846 572.253 584.885 660.214C496.924 748.175 321.002 796.154 185.062 660.214C49.1221 524.274 80.5161 338.98 198.224 221.272C315.932 103.565 510.413 40.5004 698.766 180.647C724.59 199.861 724.563 199.888 742.594 220.748" stroke="black" stroke-width="99.5167"/>
|
||||
<path d="M441.014 458.87C459.308 477.164 459.254 506.521 441.334 524.441C423.414 542.361 394.057 542.416 375.763 524.121C357.468 505.827 357.523 476.47 375.443 458.55C393.363 440.63 422.719 440.576 441.014 458.87Z" fill="black" stroke="black" stroke-width="45.2348"/>
|
||||
<path d="M741.489 291.533C711.213 260.04 671.51 214.18 643.153 198.672C675.899 179.381 750.221 152.218 779.899 187.565C816.996 231.75 774.29 325.653 741.489 291.533Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_112_17">
|
||||
<rect width="800" height="800" fill="black"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
7
doc/src/content.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineCollection } from "astro:content";
|
||||
import { docsLoader } from "@astrojs/starlight/loaders";
|
||||
import { docsSchema } from "@astrojs/starlight/schema";
|
||||
|
||||
export const collections = {
|
||||
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
|
||||
};
|
||||
113
doc/src/content/docs/developers/agent-harness.mdx
Normal file
@@ -0,0 +1,113 @@
|
||||
---
|
||||
title: Agent Harness
|
||||
description: How to use the Atomic CRM agent harness to implement CRM changes with a coordinated team of Claude Code agents.
|
||||
---
|
||||
|
||||
Atomic CRM ships an *agent harness*: a team of specialized Claude Code agents (planner, developer, reviewer, documenter) that turns a plain-English feature request into committed code through a deterministic pipeline. The agents share the same coding guidelines as the Atomic CRM core team, and add tools that cut token usage and speed up development.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- The [Claude Code CLI](https://docs.claude.com/en/docs/claude-code) installed and authenticated (an Anthropic API key or a Claude subscription).
|
||||
- Project dependencies installed (`make install`).
|
||||
- For full-stack runs only: a local Supabase instance up (`make start-supabase`).
|
||||
|
||||
## Usage
|
||||
|
||||
Start a Claude Code session as usual and ask for a new feature:
|
||||
|
||||
```
|
||||
Create a new field for lost deal reasons.
|
||||
|
||||
# or
|
||||
|
||||
Build a CRM for my company Acme, a recruiting agency.
|
||||
```
|
||||
|
||||
The harness starts immediately: Claude Code coordinates a team of specialized agents, depending on the complexity of the task. As the harness requires many permissions, you should turn on automatic permission mode to skip them.
|
||||
|
||||
<script src="https://asciinema.org/a/CcmbA8XrclBVK855.js" id="asciicast-CcmbA8XrclBVK855" data-speed="4" async="true"></script>
|
||||
|
||||
:::caution
|
||||
The automatic permissions mode lets Claude Code run arbitrary commands on your machine and may modify or delete files. We advise you to run the harness in a sandboxed environment, such as a DevContainer (already configured in the repo) or a VM.
|
||||
:::
|
||||
|
||||
:::tip
|
||||
The subagents work in a temporary directory (in `/tmp/<repo-slug>`) to keep your main checkout clean. The harness eventually backports the changes to your checkout in a branch and merges it with the current branch.
|
||||
:::
|
||||
|
||||
:::tip
|
||||
Based on the complexity of the feature request, the main thread runs one of two workflows:
|
||||
|
||||
- **Simple**: a single cosmetic edit, a single-field change on an existing entity, or a filter reusing existing components. One developer agent, no planning overhead.
|
||||
- **Complex**: everything else. The planner decomposes it into tickets; developers, reviewers, and the merger run as a coordinated wave.
|
||||
:::
|
||||
|
||||
## Inspecting a Change
|
||||
|
||||
To see exactly what a harness session produced, use the diff command from within the session:
|
||||
|
||||
```
|
||||
/harness-diff
|
||||
```
|
||||
|
||||
It shows the net set of changes the session built, isolated from any other work on your branch. If you run it from a fresh session and several harness sessions exist, it lists them so you can pick the one to inspect.
|
||||
|
||||
## Reverting a Change
|
||||
|
||||
Because the merge is automatic, the harness may make a change you're not happy with. You can use the following command to revert the current session:
|
||||
|
||||
```
|
||||
/harness-revert
|
||||
```
|
||||
|
||||
This undoes everything the session merged into your branch and cleans up its temporary branches and worktrees. By default it targets the current session; if you run it from a fresh session and several previous runs exist, it lists them and asks which to revert.
|
||||
|
||||
:::caution
|
||||
`/harness-revert` undoes the session's changes by reverting its merge commits, so it is safe even when several sessions have landed on the same branch; it removes only the selected session's work, not the others'. Uncommitted local edits are stashed first (recover them with `git stash pop`).
|
||||
:::
|
||||
|
||||
## Disabling the Harness
|
||||
|
||||
Claude Code may use the harness even though you didn't ask for it. If you don't want to use it, you can opt out in your request. For instance:
|
||||
|
||||
```
|
||||
Create a new field for lost deal reasons #no-harness
|
||||
|
||||
## or
|
||||
|
||||
Create a new field for lost deal reasons without the agent team
|
||||
|
||||
## or
|
||||
|
||||
Create a new field for lost deal reasons, skipping the agent team
|
||||
```
|
||||
|
||||
The main thread then implements the change itself, no specialized agents. Add *"no-harness for this session"* to keep it off for the whole session.
|
||||
|
||||
## Monitoring a Session
|
||||
|
||||
The harness writes a detailed trace of every agent, hook, and ticket. Three commands let you follow it:
|
||||
|
||||
```sh
|
||||
make watch # live monitor of the most recent session
|
||||
make monitor # one-shot summary of the most recent session
|
||||
make monitor SESSION=<id> # summary of a specific session
|
||||
make sessions # list known sessions, newest first
|
||||
```
|
||||
|
||||
`make watch` re-renders a live view of the active agents, hook activity, and ticket statuses, useful to keep open in a second terminal while a COMPLEX session runs. `make monitor` prints the same information as a single snapshot.
|
||||
|
||||
<script src="https://asciinema.org/a/uyaoxIH5m5QdOcbZ.js" id="asciicast-uyaoxIH5m5QdOcbZ" data-speed="4" async="true"></script>
|
||||
|
||||
|
||||
## CRM Builder: A Visual, Ready-Made Environment
|
||||
|
||||
[**CRM Builder**](https://github.com/marmelab/crm-builder) wraps the same harness in a containerized, browser-based environment, with no local setup. It adds:
|
||||
|
||||
- A containerized environment so you can run the harness without security concerns.
|
||||
- A pre-installed toolchain, no need to set up Claude Code, Node, or Supabase yourself.
|
||||
- A live preview of the CRM, updated as the harness applies your changes.
|
||||
- A session dashboard to follow each run's agents and progress.
|
||||
- Demo and full-stack mode switching from the UI.
|
||||
|
||||

|
||||
63
doc/src/content/docs/developers/architecture-choices.mdx
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
title: Architecture Choices
|
||||
description: This document explains some of the architecture decisions made in the development of Atomic CRM.
|
||||
---
|
||||
|
||||
This document explains some of the architecture decisions made in the development of Atomic CRM.
|
||||
|
||||
## Components
|
||||
|
||||

|
||||
|
||||
The frontend is a Single-Page Application built with [Shadcn Admin Kit](https://marmelab.com/shadcn-admin-kit/), a React application framework that glues together the most popular and robust React libraries, such as:
|
||||
|
||||
- [React Router](https://reactrouter.com/) for routing
|
||||
- [React Query](https://tanstack.com/query/latest) for data fetching and caching
|
||||
- [React Hook Form](https://react-hook-form.com/) for form management
|
||||
- [Shadcn UI](https://ui.shadcn.com/) for beautiful, accessible, and customizable components
|
||||
- [Radix UI](https://www.radix-ui.com/) for accessible UI components
|
||||
- [Tailwind CSS](https://tailwindcss.com/) for styling
|
||||
|
||||
For the backend, Atomic CRM leverages [Supabase](https://supabase.com/). Supabase provides a PostgreSQL database, a REST API, authentication, storage, and edge functions. In practice, this means that there is very little backend code to maintain (a few edge functions for user management and inbound email processing).
|
||||
|
||||
## Mutable Dependencies
|
||||
|
||||
The Atomic CRM code primarily lives in the `src/components/atomic-crm` directory. With less than 15,000 lines of code, it is a small codebase that can be easily customized.
|
||||
|
||||
The Atomic CRM code also contains some mutable dependencies, i.e. libraries that are directly integrated into the codebase to be modified if needed. These dependencies are located in the following directories:
|
||||
|
||||
- `src/components/admin`: [Shadcn Admin Kit](https://marmelab/shadcn-admin-kit), providing the frontend architecture and the glue code to integrate the various libraries.
|
||||
- `src/copmponents/ui`: [Shadcn UI](https://ui.shadcn.com/), providing beautiful, accessible, and customizable components powered by Tailwind CSS.
|
||||
|
||||
You can modify or replaces the files in these directories. It's the recommended way to customize the application.
|
||||
|
||||
If you want to upgrade these dependencies, you can follow the instructions in their respective documentation.
|
||||
|
||||
## Views
|
||||
|
||||
Some pages in Atomic CRM require data from multiple tables. To simplify the frontend code and reduce the HTTP overhead, Atomic CRM uses database views to abstract the complexity of the queries.
|
||||
|
||||
For instance, the contact list page displays the number of tasks for each contact. This information is provided by the `contacts_summary` view, defined in the `supabase/migrations/init_db.sql` file.
|
||||
|
||||
When using [the FakeRest data provider](../data-providers#setting-up-the-fakerest-data-provider), these views are emulated in the frontend.
|
||||
|
||||
## Triggers
|
||||
|
||||
User credentials are stored in Supabase's `auth.users` table. Supabase does not allow to add columns to this table. That's why additional user details are stored in a `sales` table created by Atomic CRM. A database trigger is used to automatically sync the `sales` record when a user is created or updated (e.g. for the `first_name` and `last_name` fields).
|
||||
|
||||
The trigger can be found in the `supabase/migrations/20240730075425_init_triggers.sql` file.
|
||||
|
||||
## Edge Functions
|
||||
|
||||
Due to the limitations of Supabase, the API does not have a public endpoint to manage users.
|
||||
|
||||
To solve this problem, Atomic CRM uses a `users` edge function in charge of:
|
||||
|
||||
- Verifying the current user's permissions
|
||||
- Creating and updating users
|
||||
|
||||
Atomic CRM does not support user deletion to avoid data losses. Yet, it is possible to disable a user's account (relying on Supabase's ban feature).
|
||||
|
||||
Atomic also uses Edge functions to handle the webhook and process the received emails. Check the [Inbound Email](../inbound-email-configuration) documentation for more information.
|
||||
|
||||
The edge functions can be found in the `supabase/functions/` directory.
|
||||
210
doc/src/content/docs/developers/atomic-crm-api.mdx
Normal file
@@ -0,0 +1,210 @@
|
||||
---
|
||||
title: REST API
|
||||
description: This document explains how to use the Atomic CRM API outside of the Supabase data provider.
|
||||
---
|
||||
|
||||
Atomic CRM exposes a complete REST API that lets you do everything you can do with the CRM.
|
||||
The API is built on top of [Supabase](https://supabase.com/docs/guides/api), that is itself built on top of [Postgrest](https://docs.postgrest.org/en/latest/).
|
||||
|
||||
## Instance API Doc In Studio
|
||||
|
||||
Use the Supabase Studio to see the [automatically generated documentation](https://supabase.com/dashboard/project/_/api) for your Atomic CRM instance. It lists all the available endpoints and their parameters. This page explains how to call each endpoint either with the supabase client, or bash (curl).
|
||||
|
||||

|
||||
|
||||
## Authentication
|
||||
|
||||
To call the API, you need to pass an `apikey` in the headers of your requests. The API key is the API key of your Supabase project, you can find it in the Supabase dashboard in the settings section.
|
||||
|
||||

|
||||
|
||||
You have two types of API keys in Supabase: the **publishable** API key, and the **secret** API key. You should use the publishable API key to call the API from your frontend or from any client side code, as it has limited permissions and does not allow to bypass RLS policies.
|
||||
You should never use the secret API key in client side code, as it gives full access to your database and can be easily extracted from your code.
|
||||
Only use the secret API key server side, and be sure to handle all authentication and security yourself if you go this way.
|
||||
|
||||
When calling protected endpoints with the publishable API key, you also need to authenticate the user and get a JWT token to be able to call the API.
|
||||
|
||||
Use the `/auth/v1/token` endpoint to authenticate a user and get a JWT token. You need to pass the user's email and password in the body of the request.
|
||||
|
||||
```sh
|
||||
curl -X POST 'https://<instance_url>/auth/v1/token?grant_type=password' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'apikey: <your_api_key>' \
|
||||
-d '{
|
||||
"email": "user@email.com",
|
||||
"password": "user_password"
|
||||
}'
|
||||
```
|
||||
|
||||
See the [Supabase documentation on self hosting auth](https://supabase.com/docs/reference/self-hosting-auth) for more details on how to setup your own auth mechanism in its entirety.
|
||||
|
||||
You then need to pass the JWT token in the `Authorization` header of your requests, with the `Bearer` scheme.
|
||||
|
||||
Example to get the list of contacts:
|
||||
|
||||
```sh
|
||||
curl -X GET 'https://<instance_url>/rest/v1/contacts' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'apikey: <your_publishable_api_key>' \
|
||||
-H 'Authorization: Bearer <your_jwt_token>'
|
||||
```
|
||||
|
||||
## OpenAPI Specification
|
||||
|
||||
The API exposes an OpenAPI specification. You need to use your API key to access it:
|
||||
|
||||
```sh
|
||||
curl https://<instance_url>/rest/v1/ \
|
||||
-H 'apikey: <your_publishable_api_key>' \
|
||||
-H 'Authorization: Bearer <your_jwt_token>'
|
||||
```
|
||||
|
||||
You can customize the OpenAPI specification via comments on tables. See the [Postgrest documentation](https://docs.postgrest.org/en/v14/references/api/openapi.html#openapi) for more details.
|
||||
|
||||
## Auth API Endpoints
|
||||
|
||||
Here is a list of the main supabase auth endpoints you are most likely to use for authentication:
|
||||
|
||||
- `POST /auth/v1/signup`: to create a new user and get a JWT token.
|
||||
- `POST /auth/v1/token`: to authenticate a user and get a JWT token. (multiple grant types are supported, check the documentation for more details)
|
||||
- `POST /auth/v1/logout`: to log out the authenticated user.
|
||||
- `GET /auth/v1/user`: to get the authenticated user information.
|
||||
|
||||
See the [Supabase project's API documentation](https://supabase.com/dashboard/project/_/integrations/data_api/docs?page=users-management) for all the possible endpoints.
|
||||
|
||||
## Data API Endpoints
|
||||
|
||||
Here is the list of all the atomic crm specific endpoints:
|
||||
|
||||
- contacts
|
||||
- `GET /rest/v1/contacts_summary`: to get the list of contacts with their company name and number of tasks.
|
||||
- `GET /rest/v1/contacts`: to get the list of contacts.
|
||||
- `POST /rest/v1/contacts`: to create a new contact.
|
||||
- `GET /rest/v1/contacts?id=eq.{id}`: to get contacts[*](#get-one-note) by id
|
||||
- `PATCH /rest/v1/contacts?id=eq.{id}`: to update a contact by id
|
||||
- `DELETE /rest/v1/contacts?id=eq.{id}`: to delete a contact by id.
|
||||
- contact notes
|
||||
- `GET /rest/v1/contact_notes`: to get the list of contact notes.
|
||||
- `POST /rest/v1/contact_notes`: to create a new contact note.
|
||||
- `GET /rest/v1/contact_notes?id=eq.{id}`: to get contact notes[*](#get-one-note) by id
|
||||
- `PATCH /rest/v1/contact_notes?id=eq.{id}`: to update a contact note by id
|
||||
- `DELETE /rest/v1/contact_notes?id=eq.{id}`: to delete a contact note by id
|
||||
- tasks
|
||||
- `GET /rest/v1/tasks`: to get the list of tasks.
|
||||
- `POST /rest/v1/tasks`: to create a new task.
|
||||
- `GET /rest/v1/tasks?id=eq.{id}`: to get tasks[*](#get-one-note) by id
|
||||
- `PATCH /rest/v1/tasks?id=eq.{id}`: to update a task by id
|
||||
- `DELETE /rest/v1/tasks?id=eq.{id}`: to delete a task by id
|
||||
- companies
|
||||
- `GET /rest/v1/companies_summary`: to get the list of companies with the number of contacts and tasks.
|
||||
- `GET /rest/v1/companies`: to get the list of companies.
|
||||
- `POST /rest/v1/companies`: to create a new company.
|
||||
- `GET /rest/v1/companies?id=eq.{id}`: to get companies[*](#get-one-note) by id
|
||||
- `PATCH /rest/v1/companies/?id=eq.{id}`: to update a company by id
|
||||
- `DELETE /rest/v1/companies?id=eq.{id}`: to delete a company by id
|
||||
- deals
|
||||
- `GET /rest/v1/deals`: to get the list of deals.
|
||||
- `POST /rest/v1/deals`: to create a new deal.
|
||||
- `GET /rest/v1/deals?id=eq.{id}`: to get deals[*](#get-one-note) by id
|
||||
- `PATCH /rest/v1/deals?id=eq.{id}`: to update a deal by id
|
||||
- `DELETE /rest/v1/deals?id=eq.{id}`: to delete a deal by id
|
||||
- deal notes
|
||||
- `GET /rest/v1/deal_notes`: to get the list of deal notes.
|
||||
- `POST /rest/v1/deal_notes`: to create a new deal note.
|
||||
- `GET /rest/v1/deal_notes?id=eq.{id}`: to get deal notes[*](#get-one-note) by id
|
||||
- `PATCH /rest/v1/deal_notes?id=eq.{id}`: to update a deal note by id
|
||||
- `DELETE /rest/v1/deal_notes?id=eq.{id}`: to delete a deal note by id
|
||||
- configuration
|
||||
- `GET /rest/v1/configuration`: to get the configuration of the CRM, including the custom fields.
|
||||
|
||||
These API endpoints are generated from the database schema via [PostgREST](https://docs.postgrest.org/en/latest/).
|
||||
|
||||
:::tip
|
||||
<a id="get-one-note"></a>
|
||||
All GET query in Postgrest will always returns a list of items by default. To have the query returns a single item directly, you can add the Accept header with the value `Accept: application/vnd.pgrst.object+json` to your request.
|
||||
:::
|
||||
|
||||
API Endpoints support all the query parameters supported by Postgrest. For example, to filter the list of contacts by status, you can use the following query:
|
||||
|
||||
```
|
||||
GET /rest/v1/contacts?status=eq.hot
|
||||
```
|
||||
|
||||
Or to get the list of tasks that are due after today, you can use the following query:
|
||||
|
||||
```
|
||||
GET /rest/v1/tasks?due_date=gt.2024-01-01
|
||||
```
|
||||
|
||||
To only get the last_name and first_name of the contacts, you can use the following query:
|
||||
|
||||
```
|
||||
GET /rest/v1/contacts?select=last_name,first_name
|
||||
```
|
||||
|
||||
:::note
|
||||
For more details on the available query parameters, and other possibilities (like calling functions) check the [Postgrest documentation](https://postgrest.org/en/stable/references/api.html).
|
||||
:::
|
||||
|
||||
## Example Usage
|
||||
|
||||
Let's say you want to integrate the CRM with another tool with a widget that allows to save a note for a contact.
|
||||
|
||||
The following example is using JavaScript and the native `fetch` API to make the HTTP requests, but you can use any programming language and any HTTP client. You could also use the `supabase` client library to do it.
|
||||
|
||||
```js
|
||||
async function addMessageAsNoteToContact({
|
||||
message,
|
||||
email,
|
||||
password,
|
||||
apikey
|
||||
}) {
|
||||
// Step 1: Authenticate the user of the widget and get a JWT token
|
||||
const authResponse = await fetch('https://<instance_url>/auth/v1/token?grant_type=password', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', apikey },
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
const authData = await authResponse.json();
|
||||
const jwtToken = authData.access_token;
|
||||
|
||||
// Step 2: Find the contact by email
|
||||
const contactsResponse = await fetch(
|
||||
`https://<instance_url>/rest/v1/contacts_summary?email_fts=ilike.*${
|
||||
encodeURIComponent(message.email)
|
||||
}*`,
|
||||
{
|
||||
headers: {
|
||||
apikey,
|
||||
Authorization: `Bearer ${jwtToken}`
|
||||
},
|
||||
}
|
||||
);
|
||||
const contacts = await contactsResponse.json();
|
||||
|
||||
if (contacts.length === 0) {
|
||||
console.log(`No contact found with email ${message.email}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const contact = contacts[0];
|
||||
|
||||
// Step 3: Add a note to this contact with the content of the message
|
||||
await fetch('https://<instance_url>/rest/v1/contact_notes', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
apikey,
|
||||
Authorization: `Bearer ${jwtToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
contact_id: contact.id,
|
||||
text: message.content,
|
||||
}),
|
||||
});
|
||||
|
||||
console.log(
|
||||
`Added note to contact ${contact.last_name} with email ${message.email}`
|
||||
);
|
||||
}
|
||||
```
|
||||
177
doc/src/content/docs/developers/custom-fields.mdx
Normal file
@@ -0,0 +1,177 @@
|
||||
---
|
||||
title: Custom Fields
|
||||
---
|
||||
|
||||
You can extend the data model of Atomic CRM by adding custom fields to the existing entities (Contacts, Companies, Deals, Notes, and Tasks).
|
||||
|
||||
## Updating The Data Model
|
||||
|
||||
The Supabase Studio provides a visual editor for the database schema. To access the Studio, go to [http://localhost:54323/](http://localhost:54323/).
|
||||
|
||||

|
||||
|
||||
We're going to add a custom field to the Contacts table: a "Referred By" text field to store the name of the person who referred the contact.
|
||||
|
||||
1. Click on the Database menu
|
||||
2. Choose the Tables section in the Database management panel. You will see the list of tables used by Atomic CRM.
|
||||
3. Click on the `contacts` table in the list of tables.
|
||||
4. Click on the "New Column" button to add a new column to the table.
|
||||
|
||||

|
||||
|
||||
5. In the "Add new column to contacts" dialog, enter the following details:
|
||||
- Name: `referred_by`
|
||||
- Type: `text`
|
||||
- Default value: leave empty
|
||||
- Allow Nullable: checked
|
||||
6. Finally, click on the "Save" button to add the new column to the table.
|
||||
|
||||

|
||||
|
||||
Atomic CRM uses views to simplify the queries. You need to update the `contacts_summary` view to include the new `referred_by` field.
|
||||
|
||||
1. Click on the "SQL Editor" menu in the left sidebar.
|
||||
2. Enter the following SQL query to update the `contacts_summary` view:
|
||||
|
||||
```sql
|
||||
drop view "public"."contacts_summary";
|
||||
create view "public"."contacts_summary"
|
||||
with (security_invoker=on)
|
||||
as
|
||||
select
|
||||
co.*,
|
||||
c.name as company_name,
|
||||
count(distinct t.id) as nb_tasks
|
||||
from
|
||||
"public"."contacts" co
|
||||
left join
|
||||
"public"."tasks" t on co.id = t.contact_id
|
||||
left join
|
||||
"public"."companies" c on co.company_id = c.id
|
||||
group by
|
||||
co.id, c.name;
|
||||
```
|
||||
|
||||

|
||||
|
||||
3. Click on the "Run" button to execute the SQL command. You will need to confirm the action since it will drop and recreate the view.
|
||||
|
||||
4. Create a [migration](../migrations/) for these schema changes, to allow them to be replicated in production. Run the following command in your terminal:
|
||||
|
||||
```sh
|
||||
npx supabase db diff -f create_referred_by
|
||||
```
|
||||
|
||||
This will create a new migration file in the `supabase/migrations` folder. Supabase will automatically apply this migration when you deploy to production.
|
||||
|
||||
That's it! You've just added a custom field to the Contacts entity. Supabase automatically updates the API to include the new field.
|
||||
|
||||
## Displaying Custom Fields in the Frontend
|
||||
|
||||
Next, you need to make this new field available in the frontend.
|
||||
|
||||
The frontend code lives in the `src/components/atomic-crm` folder, and is organized by entities. Each entity has its own folder containing all the components related to that entity. Take a few minutes to explore the codebase and familiarize yourself with its structure.
|
||||
|
||||
:::tip
|
||||
Atomic CRM leverages two libraries that provided high-level building blocks: [Shadcn Admin Kit](https://marmelab.com/shadcn-admin-kit/) for the application logic, and [Shadcn/ui](https://ui.shadcn.com/) for the UI components. The code for these libraries is copied in the `src/components` folder.
|
||||
:::
|
||||
|
||||
Atomic CRM uses TypeScript to ensure type safety, so you'll need to modify the `Contact` type to include the new `referred_by` field. Open the `src/components/atomic-crm/types.ts` file and add the `referred_by` field to the `Contact` interface.
|
||||
|
||||
```tsx {18}
|
||||
export type Contact = {
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
title: string;
|
||||
company_id: Identifier;
|
||||
email_jsonb: EmailAndType[];
|
||||
avatar?: Partial<RAFile>;
|
||||
linkedin_url?: string | null;
|
||||
first_seen: string;
|
||||
last_seen: string;
|
||||
has_newsletter: boolean;
|
||||
tags: Identifier[];
|
||||
gender: string;
|
||||
sales_id: Identifier;
|
||||
status: string;
|
||||
background: string;
|
||||
phone_jsonb: PhoneNumberAndType[];
|
||||
referred_by?: string;
|
||||
nb_tasks?: number;
|
||||
company_name?: string;
|
||||
} & Pick<RaRecord, "id">;
|
||||
```
|
||||
|
||||
You will modify the contact creation and edition form to include an input for the `referred_by` field.
|
||||
To do so, open the `src/components/atomic-crm/contacts/ContactInputs.tsx` file to edit the `<ContactInputs>` component and add a new `<TextInput>`.
|
||||
|
||||
```tsx {5-8}
|
||||
const ContactMiscInputs = () => {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<h6 className="text-lg font-semibold">Misc</h6>
|
||||
<TextInput
|
||||
source="referred_by"
|
||||
helperText={false}
|
||||
/>
|
||||
<TextInput
|
||||
source="background"
|
||||
label="Background info (bio, how you met, etc)"
|
||||
multiline
|
||||
helperText={false}
|
||||
/>
|
||||
<BooleanInput source="has_newsletter" helperText={false} />
|
||||
<ReferenceInput
|
||||
reference="sales"
|
||||
source="sales_id"
|
||||
sort={{ field: "last_name", order: "ASC" }}
|
||||
filter={{
|
||||
"disabled@neq": true,
|
||||
}}
|
||||
>
|
||||
<SelectInput
|
||||
helperText={false}
|
||||
label="Account manager"
|
||||
optionText={saleOptionRenderer}
|
||||
validate={required()}
|
||||
/>
|
||||
</ReferenceInput>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
`<TextInput>` is a Shadcn Admin Kit component that binds a form field to the form state. Upon submission, the form data will include the `referred_by` field and the Supabase API will store it in the database.
|
||||
|
||||
The application will automatically pick up this code change. Go back to [http://localhost:5173/](http://localhost:5173/) and you can now create or edit a contact and set the "Referred By" field.
|
||||
|
||||

|
||||
|
||||
Finally, update the Contacts sidebar to display the referrer when available. Open the `src/components/atomic-crm/contacts/ContactAside.tsx` file and add a new `<TextField>` for the `referred_by` field.
|
||||
|
||||
```tsx {4-11}
|
||||
export const ContactAside = ({ link = "edit" }: { link?: "edit" | "show" }) => {
|
||||
// ...
|
||||
<AsideSection title="Background info">
|
||||
{record.referred_by && (
|
||||
<div className="pb-2">
|
||||
<span className="text-sm text-muted-foreground mr-1">
|
||||
Referred by
|
||||
</span>
|
||||
<TextField source="referred_by" />
|
||||
</div>
|
||||
)}
|
||||
<WithRecord<Contact>
|
||||
render={(record) =>
|
||||
record?.background ? (
|
||||
<TextField source="background" record={record} className="pb-2" />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
This will display the "Referred By" information in the contact sidebar when available.
|
||||
|
||||

|
||||
156
doc/src/content/docs/developers/customizing.mdx
Normal file
@@ -0,0 +1,156 @@
|
||||
---
|
||||
title: Configuration
|
||||
description: This document explains how to customize the Atomic CRM application.
|
||||
---
|
||||
|
||||
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
|
||||
|
||||
The entry point of the frontend application is the `src/App.tsx` file. By default, this file simply renders the `<CRM>` component, which is the root component of Atomic CRM.
|
||||
|
||||
```tsx
|
||||
import { CRM } from '@/components/atomic-crm/root/CRM';
|
||||
|
||||
const App = () => <CRM />;
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
`<CRM>` accepts various props to customize the application domain and look and feel, so the `App.tsx` file is the best place to configure your CRM.
|
||||
|
||||
For instance, the following code snippet shows how to customize the CRM application domain-specific data.
|
||||
|
||||
```tsx
|
||||
import { CRM } from '@/components/atomic-crm/root/CRM';
|
||||
|
||||
const App = () => (
|
||||
<CRM
|
||||
companySectors={[
|
||||
{ value: 'technology', label: 'Technology' },
|
||||
{ value: 'finance', label: 'Finance' },
|
||||
]}
|
||||
dealCategories={[
|
||||
{ value: 'copywriting', label: 'Copywriting' },
|
||||
{ value: 'design', label: 'Design' },
|
||||
]}
|
||||
dealPipelineStatuses={['won']}
|
||||
dealStages={[
|
||||
{ value: 'opportunity', label: 'Opportunity' },
|
||||
{ value: 'proposal-sent', label: 'Proposal Sent' },
|
||||
{ value: 'won', label: 'Won' },
|
||||
{ value: 'lost', label: 'Lost' },
|
||||
]}
|
||||
noteStatuses={[
|
||||
{ value: 'cold', label: 'Cold', color: '#7dbde8' },
|
||||
{ value: 'warm', label: 'Warm', color: '#e8cb7d' },
|
||||
{ value: 'hot', label: 'Hot', color: '#e88b7d' },
|
||||
]}
|
||||
taskTypes={[
|
||||
{ value: 'call', label: 'Call' },
|
||||
{ value: 'email', label: 'Email' },
|
||||
{ value: 'meeting', label: 'Meeting' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
`<CRM>` accepts the following props:
|
||||
|
||||
| Props | Description | Type |
|
||||
| ---------------------- | ------------------------------------------------------------------------------------ | -------------- |
|
||||
| `companySectors` | The list of company sectors used in the application. | LabeledValue[] |
|
||||
| `currency` | The ISO 4217 currency code used to format monetary values (default: `"USD"`). | string |
|
||||
| `darkModeLogo` | Logo shown in dark mode and on the auth pages. Use an imported asset, an absolute URL, or a data URI — never a route-relative path (it breaks on nested routes like `/oauth/consent`). | string |
|
||||
| `darkTheme` | The theme to use when the application is in dark mode. | RaThemeOptions |
|
||||
| `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[] |
|
||||
| `lightModeLogo` | Logo shown in light mode. Same rule as `darkModeLogo`: imported asset, absolute URL, or data URI only. | string |
|
||||
| `lightTheme` | The theme to use when the application is in light mode. | RaThemeOptions |
|
||||
| `noteStatuses` | The statuses of notes used in the application. | NoteStatus[] |
|
||||
| `taskTypes` | The types of tasks used in the application. | LabeledValue[] |
|
||||
| `title` | The title of the CRM application. | string |
|
||||
|
||||
## Languages (i18n)
|
||||
|
||||
Atomic CRM language configuration is managed in `src/components/atomic-crm/providers/commons/i18nProvider.ts`.
|
||||
|
||||
By default, this repository ships with two locales:
|
||||
|
||||
- `en` (English)
|
||||
- `fr` (French)
|
||||
|
||||
The `i18nProvider` defines:
|
||||
|
||||
- the initial locale resolver (`getInitialLocale`, browser language with `en` fallback)
|
||||
- the list of available locales (`availableLocales`)
|
||||
- how messages are resolved for each locale
|
||||
|
||||
The language switcher appears in the Profile page when more than one locale is configured. See [User Profile](../../users/preferences#changing-your-language) for the end-user point of view.
|
||||
|
||||
### Adding a New Language
|
||||
|
||||
1. Import a translation package for the base messages (Shadcn Admin Kit comes with [35 languages integrations](https://marmelab.com/ra-core/translationlocales/))
|
||||
2. Create a message catalog for the CRM messages. Use the English messages catalog as a base (`src/components/atomic-crm/providers/commons/englishCrmMessages.ts`).
|
||||
3. Merge these two translation files using the `mergeTranslations` utility
|
||||
4. Edit `src/components/atomic-crm/providers/commons/i18nProvider.ts` and add a new locale with the merged messages to the provider.
|
||||
5. Import the new locale for date formatting in `src/components/atomic-crm/misc/RelativeDate.tsx`.
|
||||
6. Start the app, switch locale from the Profile page, and verify that the new locale is working as expected.
|
||||
|
||||
### Type-Checking Message Catalogs
|
||||
|
||||
For in-repo locales (like `fr`), use strict typing so missing keys fail at compile time:
|
||||
|
||||
```ts
|
||||
import type { CrmMessages } from './englishCrmMessages';
|
||||
|
||||
export const frenchCrmMessages = {
|
||||
crm: {
|
||||
// ...
|
||||
},
|
||||
} satisfies CrmMessages;
|
||||
```
|
||||
|
||||
For external/custom locales, you can choose either approach:
|
||||
|
||||
- strict typing (`CrmMessages`) if you want a complete catalog
|
||||
- partial typing (`PartialCrmMessages`) if you prefer incremental translations with English fallback
|
||||
|
||||
Example with partial typing:
|
||||
|
||||
```ts
|
||||
import type { PartialCrmMessages } from './englishCrmMessages';
|
||||
|
||||
const polishMessages: PartialCrmMessages = {
|
||||
crm: {
|
||||
// only override what you need
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
If an official package does not exist for a given integration (for example `ra-supabase-language-<locale>`), keep English integration messages as the base and override only the keys you need for your locale.
|
||||
|
||||
## Disabling Telemetry
|
||||
|
||||
In production, atomic-crm applications send an anonymous request on mount to a telemetry server operated by Marmelab. You can see this request by looking at the Network tab of your browser DevTools:
|
||||
|
||||
```
|
||||
https://atomic-crm-telemetry.marmelab.com/atomic-crm-telemetry
|
||||
```
|
||||
|
||||
The only data sent to the telemetry server is the admin domain (e.g. “example.com”) - no personal data is ever sent, and no cookie is included in the response. The atomic-crm team uses these domains to track the usage of the framework.
|
||||
|
||||
You can opt out of telemetry by simply adding `disableTelemetry` to the `<CRM>` component:
|
||||
|
||||
```tsx
|
||||
import { CRM } from '@/components/atomic-crm/root/CRM';
|
||||
|
||||
const App = () => <CRM disableTelemetry />;
|
||||
|
||||
export default App;
|
||||
```
|
||||
62
doc/src/content/docs/developers/data-providers.mdx
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
title: Data Providers
|
||||
description: This document explains the data providers used in the development of Atomic CRM.
|
||||
---
|
||||
|
||||
By default, the Atomic CRM uses [Supabase](https://supabase.com) for the backend API. Supabase is an open-source alternative to Firebase, built on top of Postgres. It provides a REST API and a real-time subscription system. The generous free tier allows you to run a small CRM for free.
|
||||
|
||||
## Using Test Data
|
||||
|
||||
Developing features with an empty database can be challenging. To help with this, Atomic CRM includes a CSV file with test data that can be imported into the application.
|
||||
|
||||
To import the test data, follow these steps:
|
||||
|
||||
1. Go to the contacts page.
|
||||
2. Click the "Import" button.
|
||||
3. Select the file located at `test-data/contacts.csv`.
|
||||
|
||||
## Using A Fake API For Development
|
||||
|
||||
For development purposes, you can use an alternative data provider called [FakeRest](https://github.com/marmelab/FakeRest). It's a simple REST API running in the browser that resets the data on each page reload. It's useful for testing the frontend without having to set up a backend, e.g. to let end users test some updates before the backend is ready.
|
||||
|
||||
### Running The App With FakeRest
|
||||
|
||||
Type the following command to start the application with the FakeRest data provider:
|
||||
|
||||
```bash
|
||||
npm run dev:demo
|
||||
```
|
||||
|
||||
This will start the application on [http://localhost:5173/](http://localhost:5173/) using FakeRest as the data provider. You will be logged in as a demo user with admin rights. The CRM will be pre-filled with random test data. You can modify the data, but it will be reset when you reload the page.
|
||||
|
||||
If you add custom fields and want them to be included in the FakeRest data, you can modify the data generator logic located in the [`src/components/atomic-crm/providers/fakerest/dataGenerator`](https://github.com/marmelab/atomic-crm/blob/main/src/components/atomic-crm/providers/fakerest/dataGenerator/) directory.
|
||||
|
||||
:::tip
|
||||
The [Atomic CRM demo](https://marmelab.com/atomic-crm-demo/) also uses FakeRest. Check it out to see how it works!
|
||||
:::
|
||||
|
||||
### Setting Up The FakeRest Data Provider
|
||||
|
||||
If you want to set up the FakeRest data provider manually, you need to change the `dataProvider` import in the `src/App.tsx` file:
|
||||
|
||||
```diff
|
||||
// in App.tsx
|
||||
import { CRM } from "@/components/atomic-crm/root/CRM";
|
||||
+import { authProvider, dataProvider } from "@/components/atomic-crm/providers/fakerest";
|
||||
|
||||
const App = () => (
|
||||
- <CRM />
|
||||
+ <CRM dataProvider={dataProvider} authProvider={authProvider} />
|
||||
);
|
||||
|
||||
export default App;
|
||||
|
||||
```
|
||||
|
||||
**Warning**: As supabase is still the default data provider, its environment variables (`VITE_SUPABASE_URL` and `VITE_SB_PUBLISHABLE_KEY`) are **still required**.
|
||||
|
||||
### Filters Syntax
|
||||
|
||||
The list filters used in this project MUST follow the [`ra-data-postgrest`](https://github.com/raphiniert-com/ra-data-postgrest) convention, where the filter operator is concatenated to the field name with an `@`. For example, to filter contacts by first name, you would use the `first_name@eq` filter.
|
||||
|
||||
When using FakeRest, the filters are mapped at runtime to the FakeRest filter syntax by the the [`supabaseAdapter`](https://github.com/marmelab/atomic-crm/blob/main/src/components/atomic-crm/providers/fakerest/internal/supabaseAdapter.ts) file. If a filter is not yet supported by the adapter, you have to modify this file to add support for it.
|
||||
80
doc/src/content/docs/developers/deploy.mdx
Normal file
@@ -0,0 +1,80 @@
|
||||
---
|
||||
title: Deploying to Production
|
||||
description: This document explains how to deploy Atomic CRM to production.
|
||||
---
|
||||
|
||||
## Deploying The Backend
|
||||
|
||||
The entire backend of Atomic CRM is hosted on Supabase. The backend is composed of a Postgres database, a REST API, and edge functions. Check out the [Supabase Configuration](../supabase-configuration) section for details.
|
||||
|
||||
After [configuring your Supabase instance](../supabase-configuration), you can deploy the backend changes with the following command:
|
||||
|
||||
```sh
|
||||
make supabase-deploy
|
||||
```
|
||||
|
||||
Make sure you access the frontend once to initialize the main admin account.
|
||||
|
||||
## Testing Production Mode
|
||||
|
||||
If you want to test you local frontend code using the remote Supabase instance and the production settings, you can run the following command:
|
||||
|
||||
```sh
|
||||
make prod-start
|
||||
```
|
||||
|
||||
Note: It will apply migrations and deploy edge functions.
|
||||
|
||||
You can then access the app via [`http://localhost:3000/`](http://localhost:3000/).
|
||||
|
||||
## Deploying The Frontend
|
||||
|
||||
The frontend of the CRM is a Single-Page App that can be deployed to any CDN, or to GitHub Pages.
|
||||
|
||||
First, build the frontend bundle with:
|
||||
|
||||
```sh
|
||||
make build
|
||||
```
|
||||
|
||||
This will create a `dist` directory with the built application made of static HTML, CSS, and JS files. Upload this directory to the CDN of your choice (e.g. Netlify, Vercel, etc.).
|
||||
|
||||
If you want to deploy it to GitHub pages, you can use the following command:
|
||||
|
||||
```sh
|
||||
npm run ghpages:deploy
|
||||
```
|
||||
|
||||
The CRM will be available at `https://<username>.github.io/atomic-crm/`.
|
||||
|
||||
## Deploying Updates
|
||||
|
||||
If you've modified the code, run the following command to deploy a new version of your CRM:
|
||||
|
||||
```sh
|
||||
make prod-deploy
|
||||
```
|
||||
|
||||
It will apply migrations, deploy edge functions and push the built applications to the `gh-pages` branch.
|
||||
|
||||
## Automating Deployments With GitHub Actions
|
||||
|
||||
Atomic CRM contains GitHub actions for continuous integration and delivery. To enable these actions, you will
|
||||
have to create the following secrets on GitHub:
|
||||
|
||||
```bash
|
||||
SUPABASE_ACCESS_TOKEN: Your personal access token, can be found at https://supabase.com/dashboard/account/tokens
|
||||
SUPABASE_DB_PASSWORD: Your supabase database password
|
||||
SUPABASE_PROJECT_ID: Your supabase project id
|
||||
SUPABASE_URL: Your supabase project URL
|
||||
SB_PUBLISHABLE_KEY: Your supabase project publishable key
|
||||
POSTMARK_WEBHOOK_USER: User configured in Postmark to secure the webhook
|
||||
POSTMARK_WEBHOOK_PASSWORD: Password configured in Postmark to secure the webhook
|
||||
POSTMARK_WEBHOOK_AUTHORIZED_IPS: List of IPs (comma separated) authorized to send requests to the Postmark webhook
|
||||
VITE_INBOUND_EMAIL: The email address configured in Postmark to receive inbound emails
|
||||
VITE_ATTACHMENTS_BUCKET: The name of the bucket used to store attachments. Optional default to `attachments`
|
||||
```
|
||||
|
||||
> **Note:** The `POSTMARK_*` and `VITE_INBOUND_EMAIL` variables are required for Atomic CRM's inbound email features. Have a look at the the [inbound email configuration](../inbound-email-configuration) to learn more about their usage and setup.
|
||||
|
||||
The GitHub action will run the `prod-deploy` command on every push to the `main` branch, deploying the frontend to GitHub pages and updating the Supabase instance.
|
||||
43
doc/src/content/docs/developers/getting-updates.mdx
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
title: Getting Updates
|
||||
description: How to keep a forked Atomic CRM installation up to date using the shadcn registry.
|
||||
---
|
||||
|
||||
Atomic CRM is meant to be forked and customized: there is no npm package to install as a dependency and upgrade. Instead, Atomic CRM components, hooks, and lib files are published as a [shadcn registry](https://marmelab.com/atomic-crm/r/atomic-crm.json), which you can use to pull upstream changes into your fork.
|
||||
|
||||
This registry is not meant to bootstrap a new project. If you're starting a new CRM, follow the [Installation](https://github.com/marmelab/atomic-crm#installation) instructions instead. The registry is only useful once you already have a running fork and want to catch up with upstream improvements (bug fixes, new features, dependency bumps in `src/components/admin` and `src/components/ui`).
|
||||
|
||||
## Refreshing Your Fork
|
||||
|
||||
Commit or stash any pending work first, this command overwrites files, and you'll want a clean diff to review the changes:
|
||||
|
||||
```sh
|
||||
npx shadcn add https://marmelab.com/atomic-crm/r/atomic-crm.json -o -y
|
||||
```
|
||||
|
||||
- `-o` (`--overwrite`) replaces local files with the upstream version.
|
||||
- `-y` (`--yes`) skips the interactive confirmation prompt. Prefer this flag over piping `yes |` into the command: it's the option the CLI provides for non-interactive use, and it fails loudly if the CLI ever changes its prompts instead of silently answering "yes" to whatever comes up.
|
||||
|
||||
Once the command completes, review the diff:
|
||||
|
||||
```sh
|
||||
git diff
|
||||
```
|
||||
|
||||
Resolve any conflict with your own customizations, run the test suite (`make test`, `make test-e2e`), then commit.
|
||||
|
||||
## What Gets Updated
|
||||
|
||||
The registry mirrors the files tracked by `scripts/generate-registry.mjs`: everything under `src/components/atomic-crm`, `src/components/supabase`, most of `src/hooks`, and most of `src/lib`. It does not touch your Supabase schema, your `docs/project-context.json` configuration, or any file you added outside those directories.
|
||||
|
||||
## Keeping Your Own Fork's Registry Up To Date
|
||||
|
||||
If you maintain your own fork long-term (for example to distribute your customized CRM to several deployments), regenerate and republish the registry the same way Atomic CRM's CI does:
|
||||
|
||||
```sh
|
||||
make registry-gen # regenerates registry.json from the current source tree
|
||||
make registry-build # builds the distributable registry file
|
||||
make registry-deploy # publishes it (wraps registry-build)
|
||||
```
|
||||
|
||||
See the main [README](https://github.com/marmelab/atomic-crm#shadcn-registry) for how these targets fit into the CI/CD pipeline.
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
title: Inbound Email Configuration
|
||||
description: This document explains how to configure inbound email processing in Atomic CRM.
|
||||
---
|
||||
|
||||
Atomic CRM can receive inbound emails and automatically create or update contacts based on the email content. This feature is useful to keep your CRM up-to-date with the latest interactions with your contacts.
|
||||
|
||||
Atomic CRM uses [Postmark](https://postmarkapp.com/) to receive and process inbound emails.
|
||||
|
||||
## Postmark Setup
|
||||
|
||||
You can use an existing Postmark account or [create a new one](https://account.postmarkapp.com/sign_up). The free tier allows you to send or receive up to 100 emails per month.
|
||||
|
||||
To enable inbound email features, you need to create a webhook in Postmark.
|
||||
|
||||
1. Go to your [Postmark account](https://account.postmarkapp.com/)
|
||||
2. Select your **server** (e.g. _My First Server_)
|
||||
3. Select the **Inbound Stream** (e.g. _Default Inbound Stream_)
|
||||
4. Go to the **Setup Instructions** tab
|
||||
5. Get your **server’s inbound email address** (xxxxxxx@inbound.postmarkapp.com)
|
||||
6. Set your **server’s inbound webhook URL**: `https://<user>:<password>@<project_id>.supabase.co/functions/v1/postmark` (e.g. https://user:pwd@xxxxxxx.supabase.co/functions/v1/postmark)
|
||||
7. **Save changes**. That's it for the Postmark setup!
|
||||
|
||||
> **Note**: The webhook URL includes 3 important pieces:
|
||||
> - `<user>` and `<password>` are credentials you need to choose to secure the webhook. Please note them down as you will need them in the next step.
|
||||
> - `<project_id>` is the Supabase project's `SUPABASE_PROJECT_ID`
|
||||
|
||||
> **Note**: The server’s inbound email address can be customized by registering a domain to your Postmark account.
|
||||
|
||||
## Supabase Setup
|
||||
|
||||
Atomic CRM uses a Supabase Edge Function to handle the webhook and process the received emails. You need to configure your Supabase project to handle the incoming requests.
|
||||
|
||||
In a terminal, after [linking your Supabase project](../supabase-configuration#alternative-using-an-existing-supabase-instance), run the following commands:
|
||||
|
||||
```sh
|
||||
npx supabase secrets set POSTMARK_WEBHOOK_USER=<user>
|
||||
npx supabase secrets set POSTMARK_WEBHOOK_PASSWORD=<password>
|
||||
npx supabase secrets set POSTMARK_WEBHOOK_AUTHORIZED_IPS=<comma-separated list of IPs>
|
||||
npx supabase secrets set VITE_INBOUND_EMAIL=<your-postmark-inbound-email>
|
||||
npx supabase secrets set VITE_ATTACHMENTS_BUCKET=attachments
|
||||
```
|
||||
|
||||
These commands will push the secrets to your remote Supabase project.
|
||||
|
||||
> **Note:** The list of authorized Postmark IPs can be found in the [Postmark support pages](https://postmarkapp.com/support/article/800-ips-for-firewalls#webhooks).
|
||||
|
||||
If you are using this repository's GitHub Actions to automatically deploy to your Supabase instance, you will need to set these secrets in your repository's settings.
|
||||
|
||||
```sh
|
||||
POSTMARK_WEBHOOK_USER=<user>
|
||||
POSTMARK_WEBHOOK_PASSWORD=<password>
|
||||
POSTMARK_WEBHOOK_AUTHORIZED_IPS=<comma-separated list of IPs>
|
||||
VITE_INBOUND_EMAIL=<your-postmark-inbound-email>
|
||||
```
|
||||
|
||||
## Application Configuration
|
||||
|
||||
Store the Postmark inbound email address in a `.env` file at the root of the project, so that users can easily find it in the application.
|
||||
|
||||
```sh
|
||||
VITE_INBOUND_EMAIL=xxxxxxxxxxx@inbound.postmarkapp.com
|
||||
```
|
||||
|
||||
:::tip
|
||||
You need to provide `VITE_INBOUND_EMAIL` at two places: as a Supabase Secret ([Supabase Setup](#supabase-setup)), and in the `.env` file used by the Vite app (this section).
|
||||
This is necessary because it is used both client-side, to display the Inbound Email in the Atomic CRM app, and server-side (by the Edge Function) to detect when an email is forwarded to Atomic CRM.
|
||||
:::
|
||||
|
||||
## Testing locally
|
||||
|
||||
To test inbound email features locally, you will need to do the following:
|
||||
|
||||
1. Start the Supabase Edge Functions locally: `make start-supabase-functions`
|
||||
2. Open your local Supabase instance for public access, e.g. using [ngrok](https://ngrok.com/): `ngrok http 54321`
|
||||
3. Configure your Postmark server's inbound webhook URL to point to your ngrok URL: `https://testuser:testpwd@<ngrok-url>/functions/v1/postmark` (e.g. https://testuser:testpwd@xxxxxxx.ngrok-free.app/functions/v1/postmark)
|
||||
4. Send an email to your Postmark server's inbound email address
|
||||
113
doc/src/content/docs/developers/migrations.mdx
Normal file
@@ -0,0 +1,113 @@
|
||||
---
|
||||
title: Migrations
|
||||
description: This document explains how to create and manage database migrations in Atomic CRM.
|
||||
---
|
||||
|
||||
Atomic CRM uses [declarative database schemas](https://supabase.com/docs/guides/local-development/declarative-database-schemas) to manage its database structure. The schema is defined in `.sql` files under `supabase/schemas/`, and migrations are auto-generated from those files.
|
||||
|
||||
## Schema Files
|
||||
|
||||
The `supabase/schemas/` directory contains the **source of truth** for the database structure:
|
||||
|
||||
<style>
|
||||
{`
|
||||
.sl-markdown-content table {
|
||||
font-size: 1rem;
|
||||
display: table;
|
||||
width: 100%;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
| File | Contents |
|
||||
|------|----------|
|
||||
| `01_tables.sql` | Tables, foreign keys, indexes, extensions |
|
||||
| `02_functions.sql` | All PL/pgSQL functions |
|
||||
| `03_views.sql` | Views (`contacts_summary`, `companies_summary`, `activity_log`, `init_state`) |
|
||||
| `04_triggers.sql` | All triggers (public tables + auth.users) |
|
||||
| `05_policies.sql` | Row Level Security policies |
|
||||
| `06_grants.sql` | Grants and default privileges |
|
||||
| `07_storage.sql` | Storage bucket policies |
|
||||
|
||||
Files are numbered to control load order (tables must exist before functions that reference them).
|
||||
|
||||
:::caution
|
||||
Migration files in `supabase/migrations/` are auto-generated from the schema files. In most cases, you should not edit them directly. However, there are exceptions: for example, `db diff` may generate a DROP + CREATE for a column rename instead of an ALTER TABLE RENAME. In such cases, manually edit the migration to use the correct statement, while keeping it coherent with the schema files.
|
||||
:::
|
||||
|
||||
## Working With a Local Database
|
||||
|
||||
When developing locally with `make start`, Supabase runs a local PostgreSQL instance. There are two ways to make schema changes locally.
|
||||
|
||||
### Option A: Edit Schema Files
|
||||
|
||||
1. **Edit the relevant schema file** in `supabase/schemas/`. For example, to add a column to the `contacts` table, edit `01_tables.sql`.
|
||||
|
||||
2. **Generate a migration** from your changes:
|
||||
|
||||
```sh
|
||||
npx supabase db diff --local -f <migration_name>
|
||||
```
|
||||
|
||||
This compares the schema files against the local database and generates a migration file in `supabase/migrations/`.
|
||||
|
||||
3. **Review the generated migration** in `supabase/migrations/`. In most cases it will be correct, but sometimes you may need to adjust it. For example, if you renamed a column, `db diff` may generate a DROP + CREATE instead of an `ALTER TABLE ... RENAME COLUMN`. Edit the migration file to use the correct statement, while keeping it coherent with the schema file.
|
||||
|
||||
4. **Apply the migration** locally:
|
||||
|
||||
```sh
|
||||
npx supabase migration up --local
|
||||
```
|
||||
|
||||
5. **Verify** the diff is clean (no remaining differences):
|
||||
|
||||
```sh
|
||||
npx supabase db diff --local
|
||||
```
|
||||
|
||||
This should output "No schema changes found".
|
||||
|
||||
### Option B: Use Supabase Studio
|
||||
|
||||
You can modify the schema using the local Supabase Dashboard (Studio) at http://localhost:54323/. When working on a local database, Studio automatically creates a migration for each change you make.
|
||||
|
||||
After making your changes in Studio, update the relevant schema files in `supabase/schemas/` to match, so they remain the source of truth. You can inspect the current state with:
|
||||
|
||||
```sh
|
||||
npx supabase db dump --local --schema public
|
||||
```
|
||||
|
||||
## Working With a Remote Database
|
||||
|
||||
To apply schema changes to the remote (hosted) Supabase database, push the migrations generated locally:
|
||||
|
||||
```sh
|
||||
npx supabase db push
|
||||
```
|
||||
|
||||
This applies any pending migrations from `supabase/migrations/` to the remote database.
|
||||
|
||||
If you make schema changes directly on the remote database via the hosted Supabase Studio, those changes won't be reflected in the local schema files or migration history. To re-sync, pull the remote schema and update your local files:
|
||||
|
||||
```sh
|
||||
npx supabase db pull
|
||||
```
|
||||
|
||||
This generates a new migration matching the remote state. Then update the relevant schema files in `supabase/schemas/` to match.
|
||||
|
||||
## Editing Functions
|
||||
|
||||
Function definitions in `02_functions.sql` must use the exact format that PostgreSQL stores internally (quoted identifiers, specific indentation). Otherwise, `db diff` will show phantom changes on every run.
|
||||
|
||||
After editing a function, if `db diff` shows an unexpected `CREATE OR REPLACE FUNCTION` in its output, run:
|
||||
|
||||
```sh
|
||||
npx supabase db dump --local --schema public
|
||||
```
|
||||
|
||||
Then copy the function's exact format from the dump output into `02_functions.sql`.
|
||||
|
||||
## Updating The Import Feature
|
||||
|
||||
From the frontend UI, users can import contacts and companies via a csv file.
|
||||
|
||||
If you change the data structure for a contact, don't forget to modify the sample CSV file located at [`src/components/atomic-crm/contacts/contacts_export.csv`](https://github.com/marmelab/atomic-crm/blob/main/src/components/atomic-crm/contacts/contacts_export.csv). You'll also need to modify the import function found in [`src/components/atomic-crm/contacts/useContactImport.tsx`](https://github.com/marmelab/atomic-crm/blob/main/src/components/atomic-crm/contacts/useContactImport.tsx).
|
||||
120
doc/src/content/docs/developers/sso.mdx
Normal file
@@ -0,0 +1,120 @@
|
||||
---
|
||||
title: Single Sign-On (SSO) with SAML
|
||||
description: This document explains how to configure SSO for Atomic CRM.
|
||||
---
|
||||
|
||||
Atomic CRM supports SSO (Single Sign-On) authentication through SAML 2.0. This allows your users to authenticate using their existing credentials from an identity provider, such as Google Workspace, Okta, or Microsoft Azure AD.
|
||||
|
||||
## Setup SSO with Google Workspace
|
||||
|
||||
This section explains how to set up SSO with Google Workspace as the identity provider. The steps are similar for other identity providers, but the exact UI and terminology may differ.
|
||||
|
||||
### Enable SSO in Supabase
|
||||
|
||||
In _Authentication > Sign in / Providers_, enable _SAML 2.0_:
|
||||
|
||||

|
||||
|
||||
### Create Application in Google Workspace
|
||||
|
||||
1. **Open your [Google Workplace Console](https://admin.google.com/) and go to _Apps > Web and mobile apps_**
|
||||
|
||||

|
||||
|
||||
2. **Choose to add custom SAML app**
|
||||
|
||||

|
||||
|
||||
3. **Fill out app details**
|
||||
|
||||
The information you enter here is for visibility into your Google Workspace. You can choose any values you like. _Atomic CRM_ as a name works well for most use cases. Optionally enter a description and a logo.
|
||||
|
||||

|
||||
|
||||
4. **Download IdP metadata**
|
||||
|
||||
This is a very important step. Click on _DOWNLOAD METADATA_ and save the file that was downloaded. You will need to upload this file later.
|
||||

|
||||
|
||||
|
||||
5. **Add service provider details**
|
||||
|
||||
Fill out these service provider details on the next screen. Replace `<project>` with your Supabase project identifier in the URLs below.
|
||||
|
||||
| Detail | Value |
|
||||
|--------|-------|
|
||||
| ACS URL | https://<project>.supabase.co/auth/v1/sso/saml/acs |
|
||||
| Entity ID | https://<project>.supabase.co/auth/v1/sso/saml/metadata |
|
||||
| Start URL | Your application URL (optional) |
|
||||
| Name ID format | EMAIL |
|
||||
| Name ID | Basic Information > Primary email |
|
||||
|
||||

|
||||
|
||||
6. **Configure attribute mapping**
|
||||
|
||||
To properly map the identity details to the sales fields in Atomic CRM, add the following mappings:
|
||||
|
||||
| Google Directory attributes | App attributes |
|
||||
|-----------------------------|----------------|
|
||||
| Primary email | `email` |
|
||||
| First name | `first_name` |
|
||||
| Last name | `last_name` |
|
||||
|
||||

|
||||
|
||||
7. **Configure user access**
|
||||
|
||||
You can configure which Google Workspace user accounts will get access to Atomic CRM. This is important if you wish to limit access to your software engineering teams.
|
||||
|
||||
You can configure this access by clicking on the _User access_ card (or down-arrow). Follow the instructions on screen.
|
||||

|
||||
|
||||
### Register SSO Provider in Supabase
|
||||
|
||||
Save the following JSON in a `mapping.json` file:
|
||||
|
||||
```json title="mapping.json"
|
||||
{
|
||||
"keys": {
|
||||
"email": {
|
||||
"name": "email"
|
||||
},
|
||||
"first_name": {
|
||||
"name": "first_name"
|
||||
},
|
||||
"last_name": {
|
||||
"name": "last_name"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then, run the following command where the `metadata.xml` file is the file you downloaded at _step 4_ and `mapping.json` the file you created just now. Replace `<your-project>` with your Supabase project identifier and `company.com` with your company domain:
|
||||
|
||||
```sh
|
||||
npx supabase sso add --type saml --project-ref <your-project> \
|
||||
--metadata-file /path/to/saml/metadata.xml \
|
||||
--attribute-mapping-file /path/to/mapping.json \
|
||||
--domains company.com
|
||||
```
|
||||
|
||||
### Enable Google Workspace SSO in Login Page
|
||||
|
||||
Provide the `VITE_GOOGLE_WORKPLACE_DOMAIN` environment variable, for instance by adding it to your `.env` file:
|
||||
```sh title=".env"
|
||||
...
|
||||
VITE_GOOGLE_WORKPLACE_DOMAIN=company.com
|
||||
```
|
||||
|
||||
You can optionally disable email/password authentication by providing the `VITE_DISABLE_EMAIL_PASSWORD_AUTHENTICATION` environment variable:
|
||||
```sh title=".env"
|
||||
...
|
||||
VITE_DISABLE_EMAIL_PASSWORD_AUTHENTICATION=true
|
||||
```
|
||||
|
||||
The login page will now show a button to log in with Google Workspace SSO.
|
||||
|
||||

|
||||
|
||||
When users click on this button, they will be redirected to the Google login page. After they log in, they will be redirected back to Atomic CRM and logged in automatically.
|
||||
196
doc/src/content/docs/developers/supabase-configuration.mdx
Normal file
@@ -0,0 +1,196 @@
|
||||
---
|
||||
title: Configuring Supabase
|
||||
description: This document explains how to configure a remote Supabase instance for Atomic CRM.
|
||||
---
|
||||
|
||||
Atomic CRM uses Supabase as a backend. This document explains how to configure a remote Supabase instance hosted at [supabase.com](https://supabase.com/).
|
||||
|
||||
## Creating a Remote Supabase Instance
|
||||
|
||||
Atomic CRM comes with a CLI utility to create a backend instance on [supabase.com](https://supabase.com/):
|
||||
|
||||
```sh
|
||||
make supabase-remote-init
|
||||
```
|
||||
|
||||
The script will ask you for the Supabase instance name, create the database, apply the migrations and deploy the edge functions. Finally, it will create a `.env.production.local` file with your remote Supabase configuration.
|
||||
|
||||
The script cannot yet automate the configuration of the required SMTP provider on your Supabase instance. To do that, please follow the instructions detailed in the [Email Provider Setup](#email-provider-setup) section.
|
||||
|
||||
## Alternative: Using An Existing Supabase Instance
|
||||
|
||||
If you already created a project on [supabase.com](https://supabase.com/), you can configure the Atomic CRM frontend to use it.
|
||||
|
||||
First, log into your Supabase account:
|
||||
|
||||
```sh
|
||||
npx supabase login
|
||||
```
|
||||
|
||||
Now, link the local project to the Supabase instance. You'll be asked to enter the database password.
|
||||
|
||||
```sh
|
||||
npx supabase link --project-ref ********************
|
||||
```
|
||||
|
||||
Set up secrets for the Supabase instance (follow this [guide](https://supabase.com/docs/guides/api/api-keys#where-to-find-keys) to find your publishable key):
|
||||
|
||||
```sh
|
||||
npx supabase secrets set SB_PUBLISHABLE_KEY=<publishable_key>
|
||||
```
|
||||
|
||||
Then, apply the migrations and deploy the edge functions:
|
||||
|
||||
```sh
|
||||
npx supabase db push
|
||||
npx supabase functions deploy
|
||||
```
|
||||
|
||||
Finally, add a `.env.production.local` file in the root directory with your remote Supabase credentials:
|
||||
|
||||
```sh
|
||||
VITE_SUPABASE_URL=<instance_url>
|
||||
VITE_SB_PUBLISHABLE_KEY=<instance_publishable_key>
|
||||
```
|
||||
|
||||
Atomic CRM requires that you set up an email provider on your Supabase instance. If you don't have one yet, please follow the instructions detailed in the [Email Provider Setup](#email-provider-setup) section.
|
||||
|
||||
## Testing Production Mode
|
||||
|
||||
Before deploying the frontend code to production, you may want to test the local frontend code when connected to the remote Supabase instance.
|
||||
|
||||
To do so, call the following command:
|
||||
|
||||
```sh
|
||||
make prod-start
|
||||
```
|
||||
|
||||
You will be prompted to create the first production user.
|
||||
|
||||
Using a remote Supabase instance can be interesting if you deploy from your computer, or if you want to test your app with production data in production mode.
|
||||
|
||||
## Email Provider Setup
|
||||
|
||||
In order to support [the invitations workflow](../../users/user-management#adding-users) and the default email/password authentication (including password reset), Atomic CRM needs to be able to send emails from your Supabase instance.
|
||||
|
||||
By default, Supabase provides a simple SMTP server for all projects. But this server imposes a few important restrictions and is not meant for production use:
|
||||
|
||||
- It can only send messages to pre-authorized addresses
|
||||
- It imposes significant rate-limits
|
||||
- There is no SLA guarantee on message delivery or uptime
|
||||
|
||||
Besides, it doesn't allow you to customize the sender name, so all messages will be sent from `Supabase Auth` (see the [next section](#example-configuration-with-postmark) if you want to change that).
|
||||
|
||||

|
||||
|
||||
For all these reasons, it is recommended to use a [custom SMTP provider](https://supabase.com/docs/guides/auth/auth-smtp#how-to-set-up-a-custom-smtp-server).
|
||||
|
||||
A non-exhaustive list of services that work with Supabase Auth is:
|
||||
|
||||
- [Postmark](https://postmarkapp.com/developer/user-guide/send-email-with-smtp) (recommended if you also plan to use the [Inbound Email](../../users/inbound-email) feature)
|
||||
- [Resend](https://resend.com/docs/send-with-supabase-smtp)
|
||||
- [AWS SES](https://docs.aws.amazon.com/ses/latest/dg/send-email-smtp.html)
|
||||
- [Twilio SendGrid](https://www.twilio.com/docs/sendgrid/for-developers/sending-email/getting-started-smtp)
|
||||
- [ZeptoMail](https://www.zoho.com/zeptomail/help/smtp-home.html)
|
||||
- [Brevo](https://help.brevo.com/hc/en-us/articles/7924908994450-Send-transactional-emails-using-Brevo-SMTP)
|
||||
|
||||
Once you've set up your account with an email sending service, head to the [Authentication settings page](https://supabase.com/dashboard/project/_/settings/auth) to enable and configure custom SMTP.
|
||||
|
||||
Once you save these settings, your project's Auth server will send messages to all addresses. To protect the reputation of your newly set up service a low rate-limit of 30 messages per hour is imposed. To adjust this to an acceptable value for your use case head to the [Rate Limits configuration page](https://supabase.com/dashboard/project/_/auth/rate-limits).
|
||||
|
||||
**Note:** Alternatively, you can also [set up an authentication hook](https://supabase.com/docs/guides/auth/auth-hooks/send-email-hook) to send the emails yourself.
|
||||
|
||||
### Example Configuration With Postmark
|
||||
|
||||
This section will walk you through the configuration of **Postmark** as a **custom SMTP provider** for **Supabase Auth**. This will notably allow you to customize the sender name of the authentication emails.
|
||||
|
||||

|
||||
|
||||
:::note
|
||||
While the [Inbound Email](../../users/inbound-email) feature requires using Postmark, Supabase Auth can work with any SMTP provider. But this guide assumes you are using Postmark as your SMTP provider too, since it's the recommended provider for both features.
|
||||
:::
|
||||
|
||||
**Step 1: Create a Postmark account and get your SMTP credentials**
|
||||
|
||||
You can use an existing Postmark account or [create a new one](https://account.postmarkapp.com/sign_up). The free tier allows you to send or receive up to 100 emails per month.
|
||||
|
||||
In your Server **Settings**, make sure **SMTP** is **enabled**.
|
||||
|
||||

|
||||
|
||||
Then, go to the **API Tokens** tab, and copy the **Server API token**.
|
||||
|
||||

|
||||
|
||||
**Step 2 (optional): Add new Domain or Signature**
|
||||
|
||||
If you are using Postmark in **Test Mode**, by default, it will only allow you to send emails from domains or email addresses that have been registered as **Sender Signatures** in your Postmark account. You can either add a new Sender Signature for the email address you want to send from, or add a new Domain if you want to send from any email address of that domain.
|
||||
|
||||

|
||||
|
||||
**Step 3: Configure Supabase Auth to use Postmark as a custom SMTP provider**
|
||||
|
||||
Go to the [Authentication settings page](https://supabase.com/dashboard/project/_/auth/smtp) of your Supabase project, and enable **Custom SMTP** with the following configuration:
|
||||
|
||||
| Field | Value |
|
||||
| ------------------------- | ----------------------------------------------------------------------- |
|
||||
| Sender email address | The email address you want to send from (e.g. `atomic-crm@company.com`) |
|
||||
| Sender name | The sender name you want to display in the emails (e.g. `Atomic CRM`) |
|
||||
| Host | `smtp.postmarkapp.com` |
|
||||
| Port number | `587` |
|
||||
| Minimum interval per user | You can keep the default value (60 seconds) |
|
||||
| Username | Use the Server API token you copied in Step 1 as the username |
|
||||
| Password | Use the Server API token you copied in Step 1 as the password |
|
||||
|
||||
:::tip
|
||||
Check out the [Postmark documentation](https://postmarkapp.com/developer/user-guide/send-email-with-smtp) for alternative SMTP settings.
|
||||
:::
|
||||
|
||||
:::tip
|
||||
Use the `Sender name` field to customize the sender name of the authentication emails. It will override the default name set in Postmark if you have one.
|
||||
:::
|
||||
|
||||

|
||||
|
||||
**Step 4: Make sure everything is working**
|
||||
|
||||
You can for instance click the "Forgot your password?" link on the login page to trigger a password reset email. If you receive the email with the correct sender name, then everything is working correctly!
|
||||
|
||||

|
||||
|
||||
## Setting The Login Callback
|
||||
|
||||
Atomic CRM uses Supabase's authentication system. When a user logs in, Supabase redirects them to an authentication callback URL that is handled by the frontend.
|
||||
|
||||
When developing with a local Supabase instance, the callback URL is already configured--you don't need to do anything.
|
||||
|
||||
When using a remote Supabase instance, you need to configure the callback URL as follows:
|
||||
|
||||
1. Go to the project dashboard at [supabase.com](https://supabase.com/).
|
||||
2. Go to **Authentication** > **URL Configuration**.
|
||||
3. Set up the callback URL of the production frontend in the **Site URL** field.
|
||||
|
||||
If you host Atomic CRM under the `https://example.com/atomic-crm/` URL, the callback URL should be `https://example.com/atomic-crm/auth-callback.html`.
|
||||
|
||||
## Customizing Email Templates
|
||||
|
||||
Atomic CRM uses Supabase to send authentication-related emails (confirm signup, reset password, etc).
|
||||
|
||||
When developing with a local Supabase instance, you test your custom mail templates via the [supabase TOML config](https://github.com/marmelab/atomic-crm/blob/main/supabase/config.toml) file. An example of a custom template has been done for the [recovery](https://github.com/marmelab/atomic-crm/blob/main/supabase/templates/recovery.html) email. Note that you will need to restart your supabase instance to apply the changes.
|
||||
|
||||
When using a remote Supabase instance, you can configure the email templates as follows:
|
||||
|
||||
1. Go to the project dashboard at [supabase.com](https://supabase.com/).
|
||||
2. Go to **Authentication** > **Email Templates**.
|
||||
3. Choose the template you want to change using the email template tabs.
|
||||
4. Paste the template source code in the editor and save.
|
||||
|
||||
If you want more information on how to customize email templates, check the [Customizing Email Templates](https://supabase.com/docs/guides/cli/customizing-email-templates) documentation.
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
**I have a _Security Definer View_ error in _Security Advisor_**
|
||||
|
||||
This warning informs you that the `init_state` state view is public and can be called by everybody.
|
||||
|
||||
This view is required to test if the CRM has been setup correctly. It doesn't expose any data, so you can ignore the Security Advisor error.
|
||||
362
doc/src/content/docs/index.mdx
Normal file
@@ -0,0 +1,362 @@
|
||||
---
|
||||
title: Getting Started
|
||||
description: Atomic CRM is designed to be easy to customize and extend by developers with basic React and SQL skills.
|
||||
---
|
||||
|
||||
|
||||
Atomic CRM is designed to be easy to customize and extend by developers with basic React and SQL skills. This guide will help you get started with installing, configuring, and deploying Atomic CRM.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A [GitHub](https://github.com/) account
|
||||
- [Git](https://git-scm.com/)
|
||||
- [Node.js](https://nodejs.org/en/download/) (version 20 or higher)
|
||||
- [Docker](https://docs.docker.com/get-docker/)
|
||||
- A [Supabase](https://supabase.com/) account for production deployment
|
||||
|
||||
## Installation
|
||||
|
||||
1. Fork the Atomic CRM repository on GitHub to your own account.
|
||||
|
||||

|
||||
|
||||
2. Clone your forked repository to your local machine:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/[username]/atomic-crm.git
|
||||
cd atomic-crm
|
||||
```
|
||||
|
||||
3. Install the dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
## Running Locally
|
||||
|
||||
1. Start the database and API, powered by Supabase:
|
||||
|
||||
```bash
|
||||
npx supabase start
|
||||
```
|
||||
|
||||
2. Start the development server, powered by Vite:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Atomic CRM now runs at [http://localhost:5173/](http://localhost:5173/).
|
||||
|
||||

|
||||
|
||||
:::tip
|
||||
If you need debug the backend, you can access the following services:
|
||||
|
||||
- Supabase dashboard: [http://localhost:54323/](http://localhost:54323/)
|
||||
- REST API: [http://127.0.0.1:54321](http://127.0.0.1:54321)
|
||||
- Attachments storage: [http://localhost:54323/project/default/storage/buckets/attachments](http://localhost:54323/project/default/storage/buckets/attachments)
|
||||
- Inbucket email testing service: [http://localhost:54324/](http://localhost:54324/)
|
||||
:::
|
||||
|
||||
## Admin User Setup
|
||||
|
||||
When you first access the application at [http://localhost:5173/](http://localhost:5173/), you will be prompted to create the first admin user. This user will have full access to the application and will be able to manage other users.
|
||||
|
||||
<video src="https://github.com/user-attachments/assets/2e5fa809-bd46-4ea1-926f-6f53358de681" controls className="w-full aspect-884/724"></video>
|
||||
|
||||
Once the admin user is created, you will be redirected to the dashboard. It's currently showing an onboarding screen to help you get started as long as there is no data in the database.
|
||||
|
||||
## Bootstrapping with Sample Data
|
||||
|
||||
It's easier to develop on a non-empty application. Atomic CRM provides a sample dataset you can import to get started. Click the import button and choose the sample data file at `test-data/contacts.csv`. This will add 500 contacts and 55 companies to your local database.
|
||||
|
||||

|
||||
|
||||
Now you can navigate to the Contacts list. Play with the application to get familiar with its features. Add a few notes and tasks to some contacts. This will end the onboarding and reveal the regular dashboard.
|
||||
|
||||

|
||||
|
||||
:::tip
|
||||
If you need to reset the database, stop the Supabase instance with `npx supabase stop --no-backup`, and restart it with `npx supabase start`.
|
||||
:::
|
||||
|
||||
## Configuring the Application
|
||||
|
||||
The entry point of the application is the `src/App.tsx` file. By default, this file simply renders the `<CRM>` component, which is the root component of Atomic CRM.
|
||||
|
||||
```tsx
|
||||
import { CRM } from "@/components/atomic-crm/root/CRM";
|
||||
|
||||
const App = () => <CRM />;
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
`<CRM>` accepts various props to customize the application domain and look and feel. For instance, the following code snippet shows how to customize the app title, the task types, and deal categories.
|
||||
|
||||
```tsx {5-7}
|
||||
import { CRM } from "@/components/atomic-crm/root/CRM";
|
||||
|
||||
const App = () => (
|
||||
<CRM
|
||||
title="Acme CRM"
|
||||
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' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||

|
||||
|
||||
Check the [Customizing Atomic CRM](./developers/customizing) guide for an exhaustive list of configuration settings you can customize.
|
||||
|
||||
## Adding Custom Fields
|
||||
|
||||
You can extend the data model of Atomic CRM by adding custom fields to the existing entities (Contacts, Companies, Deals, Notes, and Tasks).
|
||||
|
||||
You will do so in the Supabase Studio. To access the Studio, go to [http://localhost:54323/](http://localhost:54323/).
|
||||
|
||||

|
||||
|
||||
We're going to add a custom field to the Contacts table: a "Referred By" text field to store the name of the person who referred the contact.
|
||||
|
||||
1. Click on the Database menu
|
||||
2. Choose the Tables section in the Database management panel. You will see the list of tables used by Atomic CRM.
|
||||
3. Click on the `contacts` table in the list of tables.
|
||||
4. Click on the "New Column" button to add a new column to the table.
|
||||
|
||||

|
||||
|
||||
5. In the "Add new column to contacts" dialog, enter the following details:
|
||||
- Name: `referred_by`
|
||||
- Type: `text`
|
||||
- Default value: leave empty
|
||||
- Allow Nullable: checked
|
||||
6. Finally, click on the "Save" button to add the new column to the table.
|
||||
|
||||

|
||||
|
||||
Atomic CRM uses views to simplify the queries. You need to update the `contacts_summary` view to include the new `referred_by` field.
|
||||
|
||||
1. Click on the "SQL Editor" menu in the left sidebar.
|
||||
2. Enter the following SQL query to update the `contacts_summary` view:
|
||||
|
||||
```sql
|
||||
drop view "public"."contacts_summary";
|
||||
create view "public"."contacts_summary"
|
||||
with (security_invoker=on)
|
||||
as
|
||||
select
|
||||
co.*,
|
||||
c.name as company_name,
|
||||
count(distinct t.id) as nb_tasks
|
||||
from
|
||||
"public"."contacts" co
|
||||
left join
|
||||
"public"."tasks" t on co.id = t.contact_id
|
||||
left join
|
||||
"public"."companies" c on co.company_id = c.id
|
||||
group by
|
||||
co.id, c.name;
|
||||
```
|
||||
|
||||

|
||||
|
||||
3. Click on the "Run" button to execute the SQL command. You will need to confirm the action since it will drop and recreate the view.
|
||||
|
||||
4. Create a migration for these schema changes, to allow them to be replicated in production. Run the following command in your terminal:
|
||||
|
||||
```sh
|
||||
npx supabase db diff -f create_referred_by
|
||||
```
|
||||
|
||||
This will create a new migration file in the `supabase/migrations` folder. Supabase will automatically apply this migration when you deploy to production.
|
||||
|
||||
That's it! You've just added a custom field to the Contacts entity. Supabase automatically updates the API to include the new field.
|
||||
|
||||
## Displaying Custom Fields in the Frontend
|
||||
|
||||
Next, you need to make this new field available in the frontend.
|
||||
|
||||
The frontend code lives in the `src/components/atomic-crm` folder, and is organized by entities. Each entity has its own folder containing all the components related to that entity. Take a few minutes to explore the codebase and familiarize yourself with its structure.
|
||||
|
||||
:::tip
|
||||
Atomic CRM leverages two libraries that provided high-level building blocks: [Shadcn Admin Kit](https://marmelab.com/shadcn-admin-kit/) for the application logic, and [Shadcn/ui](https://ui.shadcn.com/) for the UI components. The code for these libraries is copied in the `src/components` folder.
|
||||
:::
|
||||
|
||||
Atomic CRM uses TypeScript to ensure type safety, so you'll need to modify the `Contact` type to include the new `referred_by` field. Open the `src/components/atomic-crm/types.ts` file and add the `referred_by` field to the `Contact` interface.
|
||||
|
||||
```tsx {18}
|
||||
export type Contact = {
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
title: string;
|
||||
company_id: Identifier;
|
||||
email_jsonb: EmailAndType[];
|
||||
avatar?: Partial<RAFile>;
|
||||
linkedin_url?: string | null;
|
||||
first_seen: string;
|
||||
last_seen: string;
|
||||
has_newsletter: boolean;
|
||||
tags: Identifier[];
|
||||
gender: string;
|
||||
sales_id: Identifier;
|
||||
status: string;
|
||||
background: string;
|
||||
phone_jsonb: PhoneNumberAndType[];
|
||||
referred_by?: string;
|
||||
nb_tasks?: number;
|
||||
company_name?: string;
|
||||
} & Pick<RaRecord, "id">;
|
||||
```
|
||||
|
||||
You will modify the contact creation and edition form to include an input for the `referred_by` field.
|
||||
To do so, open the `src/components/atomic-crm/contacts/ContactInputs.tsx` file to edit the `<ContactInputs>` component and add a new `<TextInput>`.
|
||||
|
||||
```tsx {5-8}
|
||||
const ContactMiscInputs = () => {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<h6 className="text-lg font-semibold">Misc</h6>
|
||||
<TextInput
|
||||
source="referred_by"
|
||||
helperText={false}
|
||||
/>
|
||||
<TextInput
|
||||
source="background"
|
||||
label="Background info (bio, how you met, etc)"
|
||||
multiline
|
||||
helperText={false}
|
||||
/>
|
||||
<BooleanInput source="has_newsletter" helperText={false} />
|
||||
<ReferenceInput
|
||||
reference="sales"
|
||||
source="sales_id"
|
||||
sort={{ field: "last_name", order: "ASC" }}
|
||||
filter={{
|
||||
"disabled@neq": true,
|
||||
}}
|
||||
>
|
||||
<SelectInput
|
||||
helperText={false}
|
||||
label="Account manager"
|
||||
optionText={saleOptionRenderer}
|
||||
validate={required()}
|
||||
/>
|
||||
</ReferenceInput>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
`<TextInput>` is a Shadcn Admin Kit component that binds a form field to the form state. Upon submission, the form data will include the `referred_by` field and the Supabase API will store it in the database.
|
||||
|
||||
The application will automatically pick up this code change. Go back to [http://localhost:5173/](http://localhost:5173/) and you can now create or edit a contact and set the "Referred By" field.
|
||||
|
||||

|
||||
|
||||
Finally, update the Contacts sidebar to display the referrer when available. Open the `src/components/atomic-crm/contacts/ContactAside.tsx` file and add a new `<TextField>` for the `referred_by` field.
|
||||
|
||||
```tsx {4-11}
|
||||
export const ContactAside = ({ link = "edit" }: { link?: "edit" | "show" }) => {
|
||||
// ...
|
||||
<AsideSection title="Background info">
|
||||
{record.referred_by && (
|
||||
<div className="pb-2">
|
||||
<span className="text-sm text-muted-foreground mr-1">
|
||||
Referred by
|
||||
</span>
|
||||
<TextField source="referred_by" />
|
||||
</div>
|
||||
)}
|
||||
<WithRecord<Contact>
|
||||
render={(record) =>
|
||||
record?.background ? (
|
||||
<TextField source="background" record={record} className="pb-2" />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
This will display the "Referred By" information in the contact sidebar when available.
|
||||
|
||||

|
||||
|
||||
## Deploying to Production
|
||||
|
||||
It's time to deploy your customized version of Atomic CRM to production. You use [Supabase.com](https://supabase.com) to host the backend, and GitHub Pages to host the frontend. Both services offer free plans that are sufficient for Atomic CRM.
|
||||
|
||||
First, login or register to [Supabase.com](https://supabase.com).
|
||||
|
||||
Atomic CRM comes with a CLI utility to create a backend instance on Supabase.com:
|
||||
|
||||
```sh
|
||||
make supabase-remote-init
|
||||
```
|
||||
|
||||
The script will ask you for the Supabase instance name, create the database, apply the migrations and deploy the edge functions. Finally, it will create a `.env.production.local` file with your remote Supabase configuration.
|
||||
|
||||
The frontend of the CRM is a Single-Page App that can be deployed to any CDN. Since you have forked the Atomic CRM repository on GitHub, you can use GitHub Pages to host the frontend.
|
||||
|
||||
First, build the frontend bundle with:
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
This will create a `dist` directory with the built application made of static HTML, CSS, and JS files.
|
||||
|
||||
To deploy it to GitHub pages, use the following command:
|
||||
|
||||
```sh
|
||||
npm run ghpages:deploy
|
||||
```
|
||||
|
||||
After a couple minutes, your customized CRM will be available at `https://<username>.github.io/atomic-crm/`, and connected to the production Supabase instance you created earlier.
|
||||
|
||||
You can proceed with the creation of the first admin user and start using your CRM in production!
|
||||
|
||||

|
||||
|
||||
:::tip
|
||||
Supabase.com and GitHub Pages are just two of the many services you can use to host your Atomic CRM. You can [self-host a supabase instace](https://supabase.com/docs/guides/self-hosting), and use any static server or CDN (e.g. CloudFlare, Netlify, Vercel, etc.) to host the frontend.
|
||||
:::
|
||||
|
||||
## Next Steps
|
||||
|
||||
Explore the documentation to learn more about Atomic CRM:
|
||||
|
||||
### User Documentation
|
||||
|
||||
1. [User Management](./users/user-management)
|
||||
2. [Importing And Exporting Data](./users/import-contacts)
|
||||
3. [Inbound Email](./users/inbound-email)
|
||||
|
||||
### Developer Documentation
|
||||
|
||||
- [General Concepts](./developers/architecture-choices)
|
||||
- [Using Fake Rest Data Provider for Development](./developers/data-providers)
|
||||
|
||||
- Customization
|
||||
|
||||
- [Changing The Constants](./developers/customizing)
|
||||
- [Updating The Model](./developers/migrations)
|
||||
|
||||
- Deploying to Production
|
||||
- [Configuring Supabase](./developers/supabase-configuration)
|
||||
- [Configuring Inbound Email](./developers/inbound-email-configuration) *(optional)*
|
||||
- [Deployment](./developers/deploy)
|
||||
193
doc/src/content/docs/users/import-data.mdx
Normal file
@@ -0,0 +1,193 @@
|
||||
---
|
||||
title: Importing and Exporting Data
|
||||
description: This document explains how to import and export data in Atomic CRM.
|
||||
---
|
||||
|
||||
Whether you are starting to use Atomic CRM, or want to migrate from another CRM, you can import your contacts, companies, tasks, notes, tags and deals in a few clicks. You can also export your contacts and companies in CSV format, and your contacts as vCard files.
|
||||
|
||||
## Import Data From CSV
|
||||
|
||||
Atomic CRM imports contacts, companies and deals from CSV files. Two buttons open the same import dialog:
|
||||
|
||||
- **Import data**, on the initial user onboarding page, lets you choose the resource to import into.
|
||||
- **Import CSV**, on the contacts, companies and deals pages, imports into that resource directly.
|
||||
|
||||
<video src="https://github.com/user-attachments/assets/a1133cf0-0287-4fc0-b61b-5a0b88617cfc" controls className="w-full aspect-1304/814"></video>
|
||||
|
||||
For each resource, the dialog offers a sample CSV file to download, showing the expected columns. Columns the CRM does not know are ignored, and columns you omit are left empty.
|
||||
|
||||
### Contacts
|
||||
|
||||
```csv
|
||||
first_name,last_name,gender,title,background,first_seen,last_seen,has_newsletter,status,tags,linkedin_url,company,email_work,email_home,email_other,phone_work,phone_home,phone_other
|
||||
John,Doe,male,Sales Executive,,2024-07-01T00:00:00+00:00,2024-07-01T11:54:49.95+00:00,false,in-contract,"influencer, developer",https://www.linkedin.com/in/johndoe,Acme,john@doe.example,john.doe@gmail.com,jdoe@caramail.com,659-980-2015,740.645.3807,(446) 758-2122
|
||||
Jane,Doe,female,Designer,,2024-07-01T00:00:00+00:00,2024-07-01T11:54:49.95+00:00,false,in-contract,"UI, design",https://www.linkedin.com/in/janedoe,Acme,,,jane@doe.example,659-980-2020,740.647.3802,
|
||||
```
|
||||
|
||||
When importing contacts, companies and tags will be automatically matched if they exist on the system, or imported otherwise.
|
||||
|
||||
### Companies
|
||||
|
||||
```csv
|
||||
name,sector,size,linkedin_url,website,phone_number,address,zipcode,city,state_abbr,country,description,revenue,tax_identifier
|
||||
Acme,Information Technology,50,https://www.linkedin.com/company/acme,https://acme.example,555-0100,1 Main Street,10001,New York,NY,USA,Long-time partner on print projects,5M,US-123456789
|
||||
Globex,Industrials,250,,https://globex.example,555-0111,42 Industrial Road,60601,Chicago,IL,USA,,20M,
|
||||
```
|
||||
|
||||
The `name` column is required. `sector` is matched against the company sectors configured in your CRM, either by stored value or by the label displayed in the app; a sector matching none of them is left empty.
|
||||
|
||||
### Deals
|
||||
|
||||
```csv
|
||||
name,company,category,stage,amount,expected_closing_date,description
|
||||
New website,Acme,Website design,Opportunity,12000,2026-09-30,Redesign of the public website
|
||||
Print campaign,Globex,Print project,Proposal Sent,4500,2026-10-15,
|
||||
```
|
||||
|
||||
The `name` column is required. The `company` column holds a company name: an existing company is reused, an unknown one is created. `category` and `stage` are matched against the deal categories and stages configured in your CRM, by stored value or displayed label. Since a deal always belongs to a stage, an empty or unrecognized `stage` falls back to the first stage of your pipeline.
|
||||
|
||||
## Export Contacts To CSV
|
||||
|
||||
In the contacts and companies pages, an export button allows to download the list of contacts or companies in CSV format.
|
||||
|
||||

|
||||
|
||||
## Export Contacts To vCard
|
||||
|
||||
You can also export a single contact as a vCard file from the aside panel in the Contact Edit page.
|
||||
|
||||

|
||||
|
||||
## Migrating From Another CRM
|
||||
|
||||
Atomic CRM allows to import contacts, companies, tasks, notes, tags and deals from a single JSON file. You can create this JSON file from the export of your previous CRM. The expected format of the JSON file looks like the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"sales": [
|
||||
{
|
||||
"id": 123,
|
||||
"first_name": "Barney",
|
||||
"last_name": "Calhoun",
|
||||
"email": "barney.calhoun@blackmesa.com"
|
||||
}
|
||||
],
|
||||
"companies": [
|
||||
{
|
||||
"id": 234,
|
||||
"name": "Black Mesa Research Facility",
|
||||
"sector": "industrials",
|
||||
"size": 250,
|
||||
"website": "https://blackmesa.com",
|
||||
"linkedin_url": "https://linkedin.com/company/black-mesa",
|
||||
"phone_number": "+1-505-555-0001",
|
||||
"revenue": "$500M",
|
||||
"tax_identifier": "12-3456789"
|
||||
},
|
||||
{
|
||||
"id": 345,
|
||||
"name": "Aperture Science",
|
||||
"description": "We do what we must because we can",
|
||||
"city": "Cleveland",
|
||||
"country": "USA",
|
||||
"address": "Deep underground facility",
|
||||
"zipcode": "99999",
|
||||
"state_abbr": "OH",
|
||||
"sector": "industrials",
|
||||
"size": 500,
|
||||
"linkedin_url": "https://linkedin.com/company/aperture-science",
|
||||
"website": "https://aperturescience.com",
|
||||
"phone_number": "+1-216-555-0100",
|
||||
"revenue": "$2B",
|
||||
"tax_identifier": "98-7654321",
|
||||
"context_links": [
|
||||
"https://aperturescience.com/research",
|
||||
"https://aperturescience.com/portal-technology"
|
||||
],
|
||||
"sales_id": 123
|
||||
}
|
||||
],
|
||||
"contacts": [
|
||||
{
|
||||
"id": 456,
|
||||
"first_name": "Gordon",
|
||||
"last_name": "Freeman",
|
||||
"gender": "male",
|
||||
"has_newsletter": false
|
||||
},
|
||||
{
|
||||
"id": 567,
|
||||
"first_name": "Cave",
|
||||
"last_name": "Johnson",
|
||||
"title": "CEO & Founder",
|
||||
"company_id": 345,
|
||||
"background": "Visionary entrepreneur. Believes in the power of science. May or may not have turned himself into a computer.",
|
||||
"linkedin_url": "https://linkedin.com/in/cavejohnson",
|
||||
"gender": "male",
|
||||
"has_newsletter": true,
|
||||
"sales_id": 123,
|
||||
"emails": [
|
||||
{
|
||||
"email": "cave.johnson@aperturescience.com",
|
||||
"type": "Work"
|
||||
}
|
||||
],
|
||||
"phones": [
|
||||
{
|
||||
"number": "15551234567",
|
||||
"type": "Work"
|
||||
}
|
||||
],
|
||||
"tags": ["VIP", "Science"]
|
||||
}
|
||||
],
|
||||
"notes": [
|
||||
{
|
||||
"contact_id": 456,
|
||||
"sales_id": 123,
|
||||
"text": "Still refuses to speak. Communication continues to be challenging.",
|
||||
"date": "2025-11-13T08:16:10Z"
|
||||
},
|
||||
{
|
||||
"contact_id": 567,
|
||||
"sales_id": 123,
|
||||
"text": "Discussed new testing initiative involving portals.\n\nCave mentioned something about combustible lemons. Need to follow up on R&D budget.",
|
||||
"date": "2025-02-14T09:20:21Z",
|
||||
"attachments": [
|
||||
{
|
||||
"url": "https://aperturescience.com/files/combustible-lemons-proposal.pdf",
|
||||
"name": "combustible-lemons-proposal.pdf"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tasks": [
|
||||
{
|
||||
"contact_id": 456,
|
||||
"sales_id": 123,
|
||||
"text": "Send crowbar maintenance kit"
|
||||
},
|
||||
{
|
||||
"contact_id": 567,
|
||||
"sales_id": 123,
|
||||
"text": "Schedule moon rock conversion demo",
|
||||
"due_date": "2025-03-26T16:15:00Z",
|
||||
"done_date": "2025-04-01T09:00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
To display the import page, click on the User menu in the top right corner, and click on "Import from JSON".
|
||||
|
||||

|
||||
|
||||
A form displays to upload a JSON file. You can download a sample JSON file to see the expected format.
|
||||
|
||||

|
||||
|
||||
Once you launch the import, the page will show the progress of the import, and display any error if the import fails.
|
||||
|
||||

|
||||
|
||||
This migration tool will allow you to easily migrate from your previous CRM to Atomic CRM, and start using it in a few clicks.
|
||||
82
doc/src/content/docs/users/inbound-email.mdx
Normal file
@@ -0,0 +1,82 @@
|
||||
---
|
||||
title: Inbound Email
|
||||
description: This document explains how to use inbound email in Atomic CRM.
|
||||
---
|
||||
|
||||
Atomic CRM can receive inbound emails and automatically create or update contacts based on the email content. This feature is useful to keep your CRM up-to-date with the latest interactions with your contacts.
|
||||
|
||||
## Setup
|
||||
|
||||
You must setup Postmark and Supabase to use the inbound email feature. Check the [Inbound Email Setup](../../developers/inbound-email-configuration) documentation for more information.
|
||||
|
||||
## Usage
|
||||
|
||||
Copy or forward emails to Atomic CRM's inbound email address to automatically add notes to the corresponding contacts. You can find this email address in the user menu by clicking on the "My Info" menu item, in the "Inbound Email" section. It looks like `xxxxxxx@inbound.postmarkapp.com`.
|
||||
|
||||

|
||||
|
||||
When you write to a contact in your email client, add the inbound email address in the **Bcc** field. Postmark will receive a copy of your email, and forward it to Atomic CRM. Atomic CRM uses the sender's email address to identify the user, and the recipient's email address to identify the contact. If the contact does not yet exist, Atomic CRM creates it using the email's domain as company name. The content of the email is added as a note to the contact.
|
||||
|
||||
**Tip**: Use the **Bcc** field to send emails to Atomic CRM without notifying the recipients.
|
||||
|
||||
You can also forward received emails to the inbound email address to add them as notes to the corresponding contacts.
|
||||
Atomic CRM will extract the original sender and recipient from the email content to identify the contact and sales.
|
||||
|
||||
## Example - Copying an email I sent to the CRM
|
||||
|
||||
Consider the following email:
|
||||
|
||||
```txt
|
||||
From: Jane Doe <jane.doe@marmelab.com>
|
||||
To: Kim Hegmann <kim.gegmann@acme.com>
|
||||
Cc: xxxxxxx@inbound.postmarkapp.com
|
||||
Subject: Meeting with Kim Hegmann
|
||||
|
||||
Hi Kim,
|
||||
I would like to schedule a meeting with you next week. Please let me know your availability.
|
||||
```
|
||||
|
||||
When receiving this email, Atomic CRM will:
|
||||
|
||||
- Create the _Acme_ company if it does not exist
|
||||
- Create the _Kim Hegmann_ contact if it does not exist
|
||||
- Associate both to _Jane Doe_
|
||||
- Add a note to the _Kim Hegmann_ contact with the following content:
|
||||
|
||||
```txt
|
||||
Meeting with Kim Hegmann
|
||||
|
||||
Hi Kim,
|
||||
I would like to schedule a meeting with you next week. Please let me know your availability.
|
||||
```
|
||||
|
||||
## Example - Forwarding a response to the CRM
|
||||
|
||||
Consider the following email that you forward to the inbound email address directly:
|
||||
|
||||
```txt
|
||||
From: Jane Doe <jane.doe@marmelab.com>
|
||||
To: xxxxxxx@inbound.postmarkapp.com
|
||||
Subject: Fwd: I will not be able to attend the meeting
|
||||
|
||||
---------- Forwarded message ----------
|
||||
From: Kim Hegmann <kim.hegmann@acme.com>
|
||||
To: Jane Doe <jane.doe@marmelab.com>
|
||||
Subject: I will not be able to attend the meeting
|
||||
|
||||
Hi Jane,
|
||||
I am sorry but I will not be able to attend the meeting we scheduled.
|
||||
Can we reschedule it for the week after?
|
||||
```
|
||||
|
||||
When receiving this email, Atomic CRM will:
|
||||
- Identify the _Kim Hegmann_ contact based on the original sender's email address
|
||||
- Add a note to the _Kim Hegmann_ contact with the following content:
|
||||
|
||||
```txt
|
||||
I will not be able to attend the meeting
|
||||
|
||||
Hi Jane,
|
||||
I am sorry but I will not be able to attend the meeting we scheduled.
|
||||
Can we reschedule it for the week after?
|
||||
```
|
||||
135
doc/src/content/docs/users/mcp-server.mdx
Normal file
@@ -0,0 +1,135 @@
|
||||
---
|
||||
title: MCP Server
|
||||
description: This document explains how to use MCP Server in Atomic CRM.
|
||||
---
|
||||
|
||||
You can interact with the data stored in your Atomic CRM instance using an AI assistant like Claude Desktop or Visual Studio Code, thanks to the MCP Server.
|
||||
|
||||

|
||||
|
||||
The Atomic CRM MCP server is powered by Supabase Edge Functions and Supabase OAuth, and requires no installation.
|
||||
|
||||
## Getting The MCP Server URL
|
||||
|
||||
To get the MCP Server URL, follow these steps:
|
||||
|
||||
1. Log in to your Atomic CRM instance.
|
||||
2. Click on your profile picture in the top right corner to open the user menu.
|
||||
3. Select "Profile" from the dropdown menu.
|
||||
4. In the "Profile" page, scroll down to the "MCP Server" section.
|
||||
5. Here, you will find the MCP Server URL that you can copy and use to connect your AI assistant.
|
||||
|
||||

|
||||
|
||||
## Adding The MCP Server to Your AI Assistant
|
||||
|
||||
The MCP server is currently available for the following AI assistants:
|
||||
|
||||
- Chat GPT
|
||||
- Claude.ai
|
||||
- Visual Studio Code
|
||||
- Claude Desktop
|
||||
- Claude Code
|
||||
- ~~Gemini~~
|
||||
- ~~Claude Mobile~~
|
||||
- ~~ChatGPT Desktop~~
|
||||
- ~~ChatGPT Mobile~~
|
||||
|
||||
### Chat GPT
|
||||
|
||||
Note: Atomic CRM MCP works both on the web and mobile versions of ChatGPT. You need a paid Chat GPT account to use custom connectors.
|
||||
|
||||
1. Enable the [Developer Mode](https://platform.openai.com/docs/guides/developer-mode) in [Settings → Apps → Advanced](https://chatgpt.com/apps#settings/Connectors/Advanced) settings
|
||||
2. Then, under [Settings → Apps](https://chatgpt.com/apps#settings/Connectors), click on the Create button
|
||||
3. Fill in the fields as follows:
|
||||
- Name: Atomic CRM
|
||||
- MCP Server URL: your MCP Server URL (see "Getting The MCP Server URL" section above)
|
||||
- Authentication: OAuth
|
||||
- Check the "I understand and want to continue" checkbox
|
||||
4. Click "Create"
|
||||
5. A browser window opens for you to authenticate to Atomic CRM and allow access to Claude Code.
|
||||
6. Once authenticated, you can start using the Atomic CRM extension in Chat GPT
|
||||
|
||||
If you're on a workspace or organization account, you need to ask your admin to add and approve the custom connector before you can use it.
|
||||
|
||||
### Claude.ai
|
||||
|
||||
Note: Atomic CRM MCP works both on the web and mobile versions of Claude.You need a paid Claude.ai account to use custom connectors.
|
||||
|
||||
1. Open Claude.ai and open the [Settings → Connectors](https://claude.ai/settings/connectors) page
|
||||
2. Then click on "Add custom connector"
|
||||
3. Fill in the fields as follows:
|
||||
- Name: Atomic CRM
|
||||
- MCP Server URL: your MCP Server URL (see "Getting The MCP Server URL" section above)
|
||||
4. Click "Add"
|
||||
5. A browser window opens for you to authenticate to Atomic CRM and allow access to Claude Code.
|
||||
6. Once authenticated, you can start using the Atomic CRM extension in Chat GPT
|
||||
|
||||
### Visual Studio Code
|
||||
|
||||
1. Open the command palette (Ctrl+Shift+P or Cmd+Shift+P) and run the "MCP: Add Server..." command.
|
||||
2. In the dropdown listing MCP types, choose "HTTP"
|
||||
3. Enter the MCP server URL (see "Getting The MCP Server URL" section above) when prompted.
|
||||
4. Name this server `atomic-crm` when prompted.
|
||||
5. Choose "Global" or "Local" scope as desired.
|
||||
6. A Dialog opens for you to authenticate to Atomic CRM and allow access. Click "Allow".
|
||||
7. A browser window opens for you to authenticate to Atomic CRM and allow access to Claude Code.
|
||||
8. Once authenticated, you can start using the Atomic CRM extension in Visual Studio Code
|
||||
|
||||
### Claude Desktop
|
||||
|
||||
1. Navigate to Settings > Extensions on Claude Desktop.
|
||||
2. Click on "Add a custom Extension".
|
||||
3. Fill in the fields as follows:
|
||||
- Name: Atomic CRM
|
||||
- URL: your MCP Server URL (see "Getting The MCP Server URL" section above)
|
||||
4. Click "Add".
|
||||
5. The new extension should now appear in your list of extensions. Click on "Connect" to start using it.
|
||||
6. A browser window opens for you to authenticate to Atomic CRM and allow access to Claude.
|
||||
7. Once authenticated, you can start using the Atomic CRM extension in Claude.
|
||||
|
||||
### Claude Code
|
||||
|
||||
1. Add the Atomic CRM MCP server to Claude Code by running the following command in your terminal:
|
||||
```bash
|
||||
claude mcp add atomic-crm --transport http {MCP Server URL}
|
||||
```
|
||||
|
||||
2. Open Claude Code
|
||||
```bash
|
||||
claude
|
||||
```
|
||||
|
||||
3. List the available MCP servers
|
||||
```bash
|
||||
/mcp
|
||||
```
|
||||
|
||||
4. The `atomic-crm` server should appear as "needs authentication". Press Enter to authenticate.
|
||||
5. A browser window opens for you to authenticate to Atomic CRM and allow access to Claude Code.
|
||||
6. Once authenticated, you can start using the Atomic CRM extension in Claude Code.
|
||||
|
||||
## Usage
|
||||
|
||||
Once the MCP server is enabled and authenticated, you can start asking questions about your Atomic CRM data directly from your AI assistant. For example, you can ask:
|
||||
|
||||
- "Can you remind me of the pending tasks for the hot contacts?"
|
||||
- "List all open deals with a value greater than $10,000."
|
||||
- "What are the top 5 companies by revenue?"
|
||||
|
||||
The MCP server will process your queries and return the relevant data from your Atomic CRM instance.
|
||||
|
||||
When asked about tasks, your agent will display an interactive widget with the list of tasks, allowing you to easily check them off, or navigate to the relevant contact Atomic CRM.
|
||||
|
||||

|
||||
|
||||
You can also perform actions such as creating new contacts, updating deal statuses, or generating reports using natural language commands.
|
||||
|
||||
- "Create a new contact for john.doe@acme.com"
|
||||
- "The deal #1234 has been won. Update its status."
|
||||
- "Add a follow-up task about Jeremiah: I should call him next week."
|
||||
|
||||
Finally, if you have other connectors set up, you can ask the AI assistant to combine data from Atomic CRM with data from other sources. For example:
|
||||
|
||||
- "Find the contacts I met last week and write a draft email to each of them summarizing our last conversation and suggesting next steps."
|
||||
- "Book a meeting for the Acme deal, include the deal notes in the description, and invite the contact person from that deal."
|
||||
37
doc/src/content/docs/users/merging-contacts.mdx
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: Merging Contacts
|
||||
---
|
||||
|
||||
Atomic CRM allows you to merge duplicate contacts into a single contact to maintain a clean and organized database.
|
||||
|
||||
## Selecting the Two Contacts to Merge
|
||||
|
||||
In the Contacts page, select the contact you want to merge into another, then edit it.
|
||||
|
||||
In the Contact Edit page, click on the "Merge with another contact" button on the side panel.
|
||||
|
||||

|
||||
|
||||
A dialog will appear allowing you to search for the contact you want to merge with.
|
||||
|
||||

|
||||
|
||||
## Reviewing and Confirming the Merge
|
||||
|
||||
Atomic CRM will merge the selected contact into the target contact. This means that all information from the selected contact will be transferred to the target contact, and the selected contact will be deleted.
|
||||
|
||||
When both the current contact and the target contact have a given property (like position or company), Atomic CRM keeps the one of the target contact.
|
||||
|
||||

|
||||
|
||||
Click the "Merge Contacts" button to complete the process. The current contact will be merged into the target contact, and all associated data will be consolidated.
|
||||
|
||||

|
||||
|
||||
## Causes of Duplicate Contacts
|
||||
|
||||
It is common to have duplicate contacts in your CRM. Duplicate contacts can arise from various situations, such as:
|
||||
|
||||
- Importing contacts from several CSV files that contain overlapping data.
|
||||
- Receiving an inbound email creates a new contact for a new email address instead of associating with an existing contact.
|
||||
- Manually adding contacts without checking for existing entries.
|
||||
44
doc/src/content/docs/users/mobile-app.mdx
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: Mobile App
|
||||
description: This document explains how to use the Mobile App in Atomic CRM.
|
||||
---
|
||||
|
||||
Atomic CRM also comes as a mobile application, allowing you to access your CRM data and perform actions on the go.
|
||||
|
||||
| | | |
|
||||
| -- | -- | -- |
|
||||
|  |  |  |
|
||||
|
||||
## Installation
|
||||
|
||||
Atomic CRM is actually a Progressive Web Application (PWA). This means that you can install it on your mobile device directly from your web browser.
|
||||
|
||||
To do so, simply open your Atomic CRM instance in your mobile browser and look for the "Share / Add to Home Screen" option in the browser menu. Follow the prompts to install the app on your device.
|
||||
|
||||
| | | |
|
||||
| -- | -- | -- |
|
||||
|  |  |  |
|
||||
|
||||
Then, you can open the app from your home screen and log in with your Atomic CRM credentials to access your data.
|
||||
|
||||
The app will auto-update whenever we release new features or improvements, so you will always have the latest version without needing to manually update it.
|
||||
|
||||
## Usage
|
||||
|
||||
The mobile app contains a subset of the features available in the web application, optimized for mobile use.
|
||||
|
||||
You can view and manage your contacts, tasks, and notes from the app. When adding notes, you can take advantage of the mobile device's camera to attach photos to your notes, and the microphone to use voice input.
|
||||
|
||||
| | | |
|
||||
| -- | -- | -- |
|
||||
|  |  |  |
|
||||
|
||||
To get the latest data on the page you are looking at — a note just forwarded by email, a task a colleague completed — tap the refresh button in the top bar. You can also pull the page down from the top and release it, as in most mobile apps.
|
||||
|
||||
## Offline Mode
|
||||
|
||||
The mobile app also supports offline mode, allowing you to access your data even when you don't have an internet connection.
|
||||
|
||||
:::note
|
||||
In offline mode, you will only be able to access data that has been previously loaded while you were online. This includes your contacts, tasks, and notes.
|
||||
:::
|
||||
56
doc/src/content/docs/users/preferences.mdx
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: User Profile
|
||||
description: This document explains how to change your avatar, name, email, language, and password in Atomic CRM.
|
||||
---
|
||||
|
||||
Every user can adjust their own account from the Profile page: avatar, name, email address, interface language, and password. These preferences only affect your own account. To change settings shared by the whole team (deal stages, note statuses, application name, etc.), see [Application Settings](../../users/settings) instead.
|
||||
|
||||
## Accessing Your Preferences
|
||||
|
||||
Click on your profile picture in the top right corner of the application, then select "Profile" in the dropdown menu.
|
||||
|
||||

|
||||
|
||||
The Profile page gathers all your personal preferences.
|
||||
|
||||

|
||||
|
||||
## Changing Your Avatar
|
||||
|
||||
Click on your avatar, or on the "Change" link next to it, to open the image editor.
|
||||
|
||||

|
||||
|
||||
Drop a JPEG or PNG file on the dashed area (or click it to pick a file), drag the circle to frame the picture, then click "Update Image". The "Delete" button removes your current picture.
|
||||
|
||||
The new avatar is saved immediately: you don't need to click "Edit" or "Save".
|
||||
|
||||
## Changing Your Name and Email
|
||||
|
||||
Click the "Edit" button, update the "First name", "Last name", and "Email" fields, then click "Save".
|
||||
|
||||
Your email address is also your login: once changed, sign in with the new address. It is also where Atomic CRM sends password reset and invitation emails.
|
||||
|
||||
## Changing Your Language
|
||||
|
||||
Pick a language in the "Language" selector. The interface switches immediately, and there is nothing to save.
|
||||
|
||||

|
||||
|
||||
Atomic CRM ships with English and French. The first time you open the application, the language is guessed from your browser settings, and falls back to English. Your choice is then remembered by your browser, so you may need to set it again on another computer, in another browser, or after clearing your browsing data.
|
||||
|
||||
The selector is hidden when the application is configured with a single language. To translate Atomic CRM into another language, see [Languages (i18n)](../../developers/customizing#languages-i18n) in the developer documentation.
|
||||
|
||||
## Changing Your Password
|
||||
|
||||
Click the "Change password" button: Atomic CRM sends a reset password email to your address. Follow the link in that email to choose a new password.
|
||||
|
||||
## Preferences on Mobile
|
||||
|
||||
On a mobile device, these preferences live in the Settings tab of the bottom navigation bar (see [Mobile App](../../users/mobile-app)):
|
||||
|
||||
- The "Profile" section holds your avatar, first name, last name, and email. Tap a row to edit it; each change is saved as soon as you validate it.
|
||||
- The "Preferences" section holds the language selector and a system/light/dark theme switch.
|
||||
- The "Change password" button sits at the bottom of the page, next to "Log out".
|
||||
|
||||
On desktop, the system/light/dark theme is switched with the sun/moon button in the top right corner of the application, next to your profile picture.
|
||||
29
doc/src/content/docs/users/settings.mdx
Normal file
@@ -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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
19
doc/src/content/docs/users/user-management.mdx
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: User Management
|
||||
description: This document explains how to manage users in Atomic CRM.
|
||||
---
|
||||
|
||||
## Creating the First User
|
||||
|
||||
The first time you open the Atomic CRM App, you will be prompted to create the first user. This first user will automatically be granted administrator role.
|
||||
|
||||
<video src="https://github.com/user-attachments/assets/2e5fa809-bd46-4ea1-926f-6f53358de681" controls className="w-full aspect-884/724"></video>
|
||||
|
||||
|
||||
Note: If the application is connected to the production database, the first user will need to validate their account by mail.
|
||||
|
||||
## Adding Users
|
||||
|
||||
Any user with the administrator role can create new users. To do this, click on the avatar in the top right corner and select 'Users'. From this page, you can add new users, edit existing users, and delete users.
|
||||
|
||||

|
||||
BIN
doc/src/content/images/adding-users.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
doc/src/content/images/contact-inputs-referred-by.png
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
doc/src/content/images/contact-sidebar.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
doc/src/content/images/customized-title.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
doc/src/content/images/email-custom-sender-name.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
doc/src/content/images/email-default-sender-name.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
doc/src/content/images/import-data-form.png
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
doc/src/content/images/import-data-progress.png
Normal file
|
After Width: | Height: | Size: 126 KiB |
BIN
doc/src/content/images/import-export.jpg
Normal file
|
After Width: | Height: | Size: 125 KiB |
BIN
doc/src/content/images/import.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
doc/src/content/images/inbound-email.jpg
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
doc/src/content/images/mcp-server-url.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
doc/src/content/images/merge-success.jpg
Normal file
|
After Width: | Height: | Size: 119 KiB |
BIN
doc/src/content/images/mobile-add-to-home-screen.png
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
doc/src/content/images/mobile-browser.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
doc/src/content/images/mobile-contact-edit.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
doc/src/content/images/mobile-create-note.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
doc/src/content/images/mobile-share.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
doc/src/content/images/onboarding.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
doc/src/content/images/postmark-api-token.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
doc/src/content/images/postmark-sender-signatures.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
doc/src/content/images/postmark-smtp-enabled.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
doc/src/content/images/profile-avatar.png
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
doc/src/content/images/profile-language.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
doc/src/content/images/profile-menu.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
doc/src/content/images/profile-page.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
doc/src/content/images/settings.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
doc/src/content/images/sso-google.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
doc/src/content/images/supabase-smtp-config.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
doc/src/content/images/supabase-sql-editor.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
doc/src/content/images/supabase-studio.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
doc/src/content/images/welcome-screen.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
115
doc/src/styles/global.css
Normal file
@@ -0,0 +1,115 @@
|
||||
@layer base, starlight, theme, components, utilities;
|
||||
|
||||
@import "@astrojs/starlight-tailwind";
|
||||
@import "tailwindcss/theme.css" layer(utilities);
|
||||
@import "tailwindcss/utilities.css" layer(utilities);
|
||||
|
||||
@layer utilities {
|
||||
:root {
|
||||
--sl-color-bg-nav: var(--sl-color-black);
|
||||
--sl-color-bg: var(--sl-color-black);
|
||||
--sl-color-bg-sidebar: var(--sl-color-black);
|
||||
--sl-color-hairline-shade: var(--sl-color-gray-6);
|
||||
|
||||
--color-accent-50: var(--color-indigo-50);
|
||||
--color-accent-100: var(--color-indigo-100);
|
||||
--color-accent-200: var(--color-indigo-200);
|
||||
--color-accent-300: var(--color-indigo-300);
|
||||
--color-accent-400: var(--color-indigo-400);
|
||||
--color-accent-500: var(--color-indigo-500);
|
||||
--color-accent-600: var(--color-indigo-600);
|
||||
--color-accent-700: var(--color-indigo-700);
|
||||
--color-accent-800: var(--color-indigo-800);
|
||||
--color-accent-900: var(--color-indigo-900);
|
||||
}
|
||||
}
|
||||
|
||||
.header a span,
|
||||
.header a svg {
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
[data-theme="light"] .header a span,
|
||||
[data-theme="light"] .header a svg {
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
.title-wrapper img {
|
||||
@apply size-8;
|
||||
}
|
||||
|
||||
img.icon {
|
||||
display: inline;
|
||||
box-shadow: none;
|
||||
margin: 0;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
transform: translate(0, 40%);
|
||||
margin-top: -16px;
|
||||
margin-left: -4px;
|
||||
margin-right: -4px;
|
||||
}
|
||||
|
||||
.rcg-tab {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.expressive-code button.cb-fullscreen__button {
|
||||
color: var(--ec-frm-inlBtnFg);
|
||||
}
|
||||
|
||||
/* Auth and Data Provider icons */
|
||||
|
||||
.providers-list img {
|
||||
width: 25px !important;
|
||||
margin: 2px 10px 2px 0px !important;
|
||||
box-shadow: none !important;
|
||||
vertical-align: middle;
|
||||
display: inline;
|
||||
}
|
||||
.providers-list ul {
|
||||
padding-left: 0;
|
||||
}
|
||||
.providers-list ul > li {
|
||||
list-style-type: none;
|
||||
}
|
||||
.providers-list .flex {
|
||||
display: flex !important;
|
||||
text-align: -webkit-match-parent !important;
|
||||
align-items: center !important;
|
||||
flex-wrap: wrap;
|
||||
margin: 0;
|
||||
}
|
||||
.providers-list .avatar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: DarkGray;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin: 2px 10px 2px 0px;
|
||||
border-radius: 100%;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/*
|
||||
Improve props table layout
|
||||
*/
|
||||
.sl-markdown-content table {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.sl-markdown-content table code {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.sl-markdown-content table tr td:has(code) {
|
||||
max-width: 140px;
|
||||
}
|
||||
.sl-markdown-content table tr td code {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
li ul,
|
||||
li p {
|
||||
margin-block: 0;
|
||||
padding-block: 0;
|
||||
}
|
||||
5
doc/tsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"include": [".astro/types.d.ts", "**/*"],
|
||||
"exclude": ["dist"]
|
||||
}
|
||||