CognxSafeTrack commited on
Commit ·
55a1607
1
Parent(s): 93faeee
feat: add organization useCase selection to modal and backend
Browse files
apps/admin/src/pages/ClientsManagementView.tsx
CHANGED
|
@@ -9,6 +9,8 @@ interface Organization {
|
|
| 9 |
id: string;
|
| 10 |
name: string;
|
| 11 |
status: 'ACTIVE' | 'PENDING' | 'CONFIG_REQUIRED';
|
|
|
|
|
|
|
| 12 |
wabaId?: string;
|
| 13 |
phoneNumbers?: { id: string, phoneNumber: string }[];
|
| 14 |
lastActivity?: string;
|
|
@@ -41,7 +43,8 @@ export default function ClientsManagementView() {
|
|
| 41 |
slug: '',
|
| 42 |
adminEmail: '',
|
| 43 |
adminName: '',
|
| 44 |
-
mode: 'PEDAGOGY' as
|
|
|
|
| 45 |
});
|
| 46 |
const [isCreating, setIsCreating] = useState(false);
|
| 47 |
const [showGuide, setShowGuide] = useState(false);
|
|
@@ -69,7 +72,7 @@ export default function ClientsManagementView() {
|
|
| 69 |
await api.post('/v1/organizations', newOrg, token!);
|
| 70 |
await fetchClients();
|
| 71 |
setIsModalOpen(false);
|
| 72 |
-
setNewOrg({ name: '', slug: '', adminEmail: '', adminName: '', mode: 'PEDAGOGY' });
|
| 73 |
} catch (error) {
|
| 74 |
console.error("Failed to create organization:", error);
|
| 75 |
alert("Erreur lors de la création de l'organisation. Vérifiez que le slug est unique.");
|
|
@@ -287,6 +290,25 @@ export default function ClientsManagementView() {
|
|
| 287 |
<option value="CUSTOMER_SERVICE">Service Client IA</option>
|
| 288 |
</select>
|
| 289 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 290 |
</div>
|
| 291 |
|
| 292 |
<div className="p-6 bg-slate-50 rounded-2xl border border-slate-100 space-y-4">
|
|
|
|
| 9 |
id: string;
|
| 10 |
name: string;
|
| 11 |
status: 'ACTIVE' | 'PENDING' | 'CONFIG_REQUIRED';
|
| 12 |
+
mode: 'EDTECH' | 'CRM_MARKETING' | 'PEDAGOGY' | 'CUSTOMER_SERVICE';
|
| 13 |
+
useCase: 'EDUCATION' | 'CRM_WHATSAPP';
|
| 14 |
wabaId?: string;
|
| 15 |
phoneNumbers?: { id: string, phoneNumber: string }[];
|
| 16 |
lastActivity?: string;
|
|
|
|
| 43 |
slug: '',
|
| 44 |
adminEmail: '',
|
| 45 |
adminName: '',
|
| 46 |
+
mode: 'PEDAGOGY' as 'CRM_MARKETING' | 'PEDAGOGY' | 'CUSTOMER_SERVICE',
|
| 47 |
+
useCase: 'EDUCATION' as 'EDUCATION' | 'CRM_WHATSAPP'
|
| 48 |
});
|
| 49 |
const [isCreating, setIsCreating] = useState(false);
|
| 50 |
const [showGuide, setShowGuide] = useState(false);
|
|
|
|
| 72 |
await api.post('/v1/organizations', newOrg, token!);
|
| 73 |
await fetchClients();
|
| 74 |
setIsModalOpen(false);
|
| 75 |
+
setNewOrg({ name: '', slug: '', adminEmail: '', adminName: '', mode: 'PEDAGOGY', useCase: 'EDUCATION' });
|
| 76 |
} catch (error) {
|
| 77 |
console.error("Failed to create organization:", error);
|
| 78 |
alert("Erreur lors de la création de l'organisation. Vérifiez que le slug est unique.");
|
|
|
|
| 290 |
<option value="CUSTOMER_SERVICE">Service Client IA</option>
|
| 291 |
</select>
|
| 292 |
</div>
|
| 293 |
+
<div className="col-span-2">
|
| 294 |
+
<label className="block text-xs font-bold uppercase text-slate-400 mb-2">Espace de travail (Workstream)</label>
|
| 295 |
+
<select
|
| 296 |
+
value={newOrg.useCase}
|
| 297 |
+
onChange={e => {
|
| 298 |
+
const val = e.target.value as any;
|
| 299 |
+
setNewOrg({
|
| 300 |
+
...newOrg,
|
| 301 |
+
useCase: val,
|
| 302 |
+
// Sync mode for convenience if they pick CRM
|
| 303 |
+
mode: val === 'CRM_WHATSAPP' ? 'CRM_MARKETING' : 'PEDAGOGY'
|
| 304 |
+
});
|
| 305 |
+
}}
|
| 306 |
+
className="w-full border border-slate-200 rounded-xl px-4 py-3 outline-none focus:ring-2 focus:ring-slate-900 transition bg-white"
|
| 307 |
+
>
|
| 308 |
+
<option value="EDUCATION">🎓 Plateforme Éducative (Modules)</option>
|
| 309 |
+
<option value="CRM_WHATSAPP">💬 CRM & Messages WhatsApp (Excel)</option>
|
| 310 |
+
</select>
|
| 311 |
+
</div>
|
| 312 |
</div>
|
| 313 |
|
| 314 |
<div className="p-6 bg-slate-50 rounded-2xl border border-slate-100 space-y-4">
|
apps/api/src/routes/organizations.ts
CHANGED
|
@@ -13,7 +13,8 @@ export async function organizationRoutes(fastify: FastifyInstance) {
|
|
| 13 |
slug: z.string().min(3).regex(/^[a-z0-9-]+$/),
|
| 14 |
adminEmail: z.string().email(),
|
| 15 |
adminName: z.string().min(2),
|
| 16 |
-
mode: z.enum(['CRM_MARKETING', 'PEDAGOGY', 'CUSTOMER_SERVICE']).default('PEDAGOGY')
|
|
|
|
| 17 |
});
|
| 18 |
|
| 19 |
const PersonalityConfigSchema = z.object({
|
|
@@ -42,7 +43,7 @@ export async function organizationRoutes(fastify: FastifyInstance) {
|
|
| 42 |
const body = OrganizationCreationSchema.safeParse(req.body);
|
| 43 |
if (!body.success) return reply.code(400).send({ error: body.error.flatten() });
|
| 44 |
|
| 45 |
-
const { adminEmail, adminName, slug, mode, ...orgData } = body.data;
|
| 46 |
|
| 47 |
// Check if slug already exists
|
| 48 |
const existing = await prisma.organization.findUnique({ where: { slug } });
|
|
@@ -53,7 +54,7 @@ export async function organizationRoutes(fastify: FastifyInstance) {
|
|
| 53 |
// Use a transaction to ensure Org, Admin and Email Queueing are linked
|
| 54 |
const result = await prisma.$transaction(async (tx) => {
|
| 55 |
const org = await tx.organization.create({
|
| 56 |
-
data: { ...data, slug, mode }
|
| 57 |
});
|
| 58 |
|
| 59 |
// Temporary password (user will reset it)
|
|
|
|
| 13 |
slug: z.string().min(3).regex(/^[a-z0-9-]+$/),
|
| 14 |
adminEmail: z.string().email(),
|
| 15 |
adminName: z.string().min(2),
|
| 16 |
+
mode: z.enum(['CRM_MARKETING', 'PEDAGOGY', 'CUSTOMER_SERVICE']).default('PEDAGOGY'),
|
| 17 |
+
useCase: z.enum(['EDUCATION', 'CRM_WHATSAPP']).default('EDUCATION')
|
| 18 |
});
|
| 19 |
|
| 20 |
const PersonalityConfigSchema = z.object({
|
|
|
|
| 43 |
const body = OrganizationCreationSchema.safeParse(req.body);
|
| 44 |
if (!body.success) return reply.code(400).send({ error: body.error.flatten() });
|
| 45 |
|
| 46 |
+
const { adminEmail, adminName, slug, mode, useCase, ...orgData } = body.data;
|
| 47 |
|
| 48 |
// Check if slug already exists
|
| 49 |
const existing = await prisma.organization.findUnique({ where: { slug } });
|
|
|
|
| 54 |
// Use a transaction to ensure Org, Admin and Email Queueing are linked
|
| 55 |
const result = await prisma.$transaction(async (tx) => {
|
| 56 |
const org = await tx.organization.create({
|
| 57 |
+
data: { ...data, slug, mode, useCase }
|
| 58 |
});
|
| 59 |
|
| 60 |
// Temporary password (user will reset it)
|
packages/database/prisma/schema.prisma
CHANGED
|
@@ -21,6 +21,7 @@ model Organization {
|
|
| 21 |
flowConfig Json?
|
| 22 |
knowledgeBaseUrl String?
|
| 23 |
mode OrganizationMode @default(EDTECH)
|
|
|
|
| 24 |
webhookSecret String?
|
| 25 |
webhookUrl String?
|
| 26 |
openAiApiKey String?
|
|
@@ -400,6 +401,11 @@ enum OrganizationMode {
|
|
| 400 |
CUSTOMER_SERVICE
|
| 401 |
}
|
| 402 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 403 |
enum EnrollmentStatus {
|
| 404 |
ACTIVE
|
| 405 |
COMPLETED
|
|
|
|
| 21 |
flowConfig Json?
|
| 22 |
knowledgeBaseUrl String?
|
| 23 |
mode OrganizationMode @default(EDTECH)
|
| 24 |
+
useCase OrganizationUseCase @default(EDUCATION)
|
| 25 |
webhookSecret String?
|
| 26 |
webhookUrl String?
|
| 27 |
openAiApiKey String?
|
|
|
|
| 401 |
CUSTOMER_SERVICE
|
| 402 |
}
|
| 403 |
|
| 404 |
+
enum OrganizationUseCase {
|
| 405 |
+
EDUCATION
|
| 406 |
+
CRM_WHATSAPP
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
enum EnrollmentStatus {
|
| 410 |
ACTIVE
|
| 411 |
COMPLETED
|
packages/shared-types/src/organization.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
| 1 |
|
| 2 |
import { z } from 'zod';
|
| 3 |
|
|
|
|
|
|
|
|
|
|
| 4 |
export const SectorSchema = z.object({
|
| 5 |
id: z.string(),
|
| 6 |
label: z.string(),
|
|
@@ -27,6 +30,7 @@ export const OrganizationSchema = z.object({
|
|
| 27 |
contactEmail: z.string().email().optional(),
|
| 28 |
customPrompt: z.string().optional(),
|
| 29 |
mode: z.enum(['EDTECH', 'WEBHOOK', 'AI_AGENT', 'CRM_MARKETING', 'PEDAGOGY', 'CUSTOMER_SERVICE']).optional(),
|
|
|
|
| 30 |
flowConfig: FlowConfigSchema.optional(),
|
| 31 |
webhookUrl: z.string().url().optional().or(z.literal('')),
|
| 32 |
webhookSecret: z.string().optional(),
|
|
|
|
| 1 |
|
| 2 |
import { z } from 'zod';
|
| 3 |
|
| 4 |
+
export const OrganizationUseCaseSchema = z.enum(['EDUCATION', 'CRM_WHATSAPP']);
|
| 5 |
+
export type OrganizationUseCase = z.infer<typeof OrganizationUseCaseSchema>;
|
| 6 |
+
|
| 7 |
export const SectorSchema = z.object({
|
| 8 |
id: z.string(),
|
| 9 |
label: z.string(),
|
|
|
|
| 30 |
contactEmail: z.string().email().optional(),
|
| 31 |
customPrompt: z.string().optional(),
|
| 32 |
mode: z.enum(['EDTECH', 'WEBHOOK', 'AI_AGENT', 'CRM_MARKETING', 'PEDAGOGY', 'CUSTOMER_SERVICE']).optional(),
|
| 33 |
+
useCase: OrganizationUseCaseSchema.default('EDUCATION').optional(),
|
| 34 |
flowConfig: FlowConfigSchema.optional(),
|
| 35 |
webhookUrl: z.string().url().optional().or(z.literal('')),
|
| 36 |
webhookSecret: z.string().optional(),
|