@wisemen/wise-crm-web
v1.3.1
Published
CRM frontend package with Vue 3 components, composables and types
Downloads
1,253
Readme
@wisemen/wise-crm-web
A comprehensive CRM component library for Vue 3 applications, providing ready-to-use components, composables, and utilities for building CRM features.
Table of Contents
- Installation
- Quick Start
- Configuration
- Components
- Composables
- Routing
- Internationalization
- Icons
- Testing Utilities
- Type System
Installation
# In a monorepo with pnpm workspaces
pnpm add @wisemen/wise-crm-web@workspace:*Or for external projects:
npm install @wisemen/wise-crm-webQuick Start
1. Initialize the CRM
Initialize the CRM configuration in your root layout or app entry point:
<script setup lang="ts">
import { useWiseCrmConfig } from '@wisemen/wise-crm-web'
import { BoardColumnTransitionTriggerType } from '@wisemen/wise-crm-web/client'
const wiseCrm = useWiseCrmConfig()
wiseCrm.init({
apiCrmBaseUrl: 'https://api.your-crm.com',
getAccessToken: () => oAuthClient.getAccessToken(),
googleMapsApiKey: 'YOUR_GOOGLE_MAPS_API_KEY',
locale: 'en-US',
options: {
deals: {
allowedTransitionTriggerTypes: [
BoardColumnTransitionTriggerType.EMAIL,
BoardColumnTransitionTriggerType.MANUAL,
],
},
widgets: {
entityStatistics: {
viewAllBusinessRouteName: 'crm-businesses',
viewAllIndividualRouteName: 'crm-individuals',
},
recentlyCreatedEntities: true,
},
},
})
</script>2. Set Up Routes
Configure CRM routes in your router:
import { useWiseCrmRoutes } from '@wisemen/wise-crm-web'
const routes = [
// ... your other routes
useWiseCrmRoutes({
businessTabRoutes: [
{
label: computed(() => 'Information'),
route: {
name: 'crm-business-information',
component: BusinessInformationTabView,
},
},
],
individualTabRoutes: [
{
label: computed(() => 'Information'),
route: {
name: 'crm-individual-information',
component: IndividualInformationTabView,
},
},
],
dealTabRoutes: [
{
label: computed(() => 'Information'),
route: {
name: 'crm-deal-information',
component: DealInformationTabView,
},
},
],
excludedReturnRoutes: [], // Optional: routes to exclude from return navigation
}),
]3. Integrate i18n and Icons
// i18n setup
import { wiseCrmLocales } from '@wisemen/wise-crm-web'
const messages = {
'en-US': {
...yourEnLocale,
...wiseCrmLocales['en-US'],
},
'nl-BE': {
...yourNlLocale,
...wiseCrmLocales['nl-BE'],
},
}
// Icons setup
import { wiseCrmIcons } from '@wisemen/wise-crm-web'
export const icons = {
...wiseCrmIcons,
...yourCustomIcons,
}Configuration
useWiseCrmConfig().init(options)
Initialize the CRM with the following configuration options:
Required Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| apiCrmBaseUrl | string | Base URL for the CRM API |
| getAccessToken | () => Promise<string> | Function that returns the current authentication token |
| googleMapsApiKey | string | Google Maps API key for address autocomplete |
| locale | Locale | Initial locale ('en-US' or 'nl-BE') |
Options
options.deals
Configure deal/pipeline behavior:
{
deals: {
// Types of transitions allowed in the deal board
allowedTransitionTriggerTypes: BoardColumnTransitionTriggerType[]
} | null
}Available transition types:
BoardColumnTransitionTriggerType.MANUAL- Manual drag & dropBoardColumnTransitionTriggerType.EMAIL- Email-triggered transitions
options.widgets
Configure widget behaviors:
{
widgets: {
entityStatistics?: {
// Route names for "View All" buttons in statistics widgets
viewAllBusinessRouteName: string
viewAllIndividualRouteName: string
}
// Enable recently created entities widget
recentlyCreatedEntities?: true
}
}Components
Overview Components
WiseCrmOverviewContent
Main CRM dashboard with statistics widgets and recent activity.
<template>
<WiseCrmOverviewContent />
</template>Deal Components
WiseCrmCrmDealOverview
Kanban-style deal pipeline board with drag-and-drop.
<template>
<WiseCrmCrmDealOverview />
</template>Dialog Components
WiseCrmContactCreateDialog
Dialog for adding contact persons to a business.
<script setup lang="ts">
import { useWiseCrmContactCreateDialog, WiseCrmContactCreateDialog } from '@wisemen/wise-crm-web'
const contactDialog = useWiseCrmContactCreateDialog()
// Open from anywhere in your app
function addContact(): void {
contactDialog.open(businessUuid)
}
</script>
<template>
<WiseCrmContactCreateDialog />
</template>Card Components
WiseCrmBusinessUpdateCard
Form card for updating business information.
<script setup lang="ts">
import { useRouteParams } from '@vueuse/router'
import type { WiseCrmBusinessUuid } from '@wisemen/wise-crm-web'
import { WiseCrmBusinessUpdateCard } from '@wisemen/wise-crm-web'
const businessUuid = useRouteParams<WiseCrmBusinessUuid>('businessUuid', null)
</script>
<template>
<WiseCrmBusinessUpdateCard
v-if="businessUuid !== null"
:business-uuid="businessUuid"
/>
</template>Props:
businessUuid:WiseCrmBusinessUuid- The business to update
WiseCrmBusinessBillingInformationCard
Form card for managing business billing information.
<template>
<WiseCrmBusinessBillingInformationCard :business-uuid="businessUuid" />
</template>Props:
businessUuid:WiseCrmBusinessUuid- The business UUID
WiseCrmIndividualUpdateCard
Form card for updating individual information.
<template>
<WiseCrmIndividualUpdateCard :individual-uuid="individualUuid" />
</template>Props:
individualUuid:WiseCrmIndividualUuid- The individual UUID
WiseCrmIndividualBillingInformationCard
Form card for managing individual billing information.
<template>
<WiseCrmIndividualBillingInformationCard :individual-uuid="individualUuid" />
</template>Props:
individualUuid:WiseCrmIndividualUuid- The individual UUID
Widget Components
WiseCrmBusinessWidget
Statistics widget showing business metrics.
<template>
<WiseCrmBusinessWidget />
</template>WiseCrmIndividualWidget
Statistics widget showing individual metrics.
<template>
<WiseCrmIndividualWidget />
</template>WiseCrmRecentlyCreatedWidget
Widget displaying recently created entities.
<template>
<WiseCrmRecentlyCreatedWidget />
</template>Composables
useWiseCrm()
Main composable for CRM operations and navigation.
import { useWiseCrm } from '@wisemen/wise-crm-web'
const crm = useWiseCrm()Methods
openBusinessDetail(businessUuid: WiseCrmBusinessUuid): void
Navigate to business detail page.
crm.openBusinessDetail(businessUuid)openIndividualDetail(individualUuid: WiseCrmIndividualUuid): void
Navigate to individual detail page.
crm.openIndividualDetail(individualUuid)getEntityConfig(type: EntityType): EntityConfig | null
Get configuration for a specific entity type.
const businessConfig = crm.getEntityConfig(EntityType.BUSINESS)
console.log(businessConfig?.name) // Localized name like "Business" or "Bedrijf"goToReturnRoute(): void
Navigate back to the stored return route (useful for "back" buttons).
crm.goToReturnRoute()storeReturnRoute(route: RouteLocationNormalized, excludedRoutes?: (string | RegExp)[]): void
Manually store a return route (usually handled automatically by useWiseCrmRoutes).
crm.storeReturnRoute(router.currentRoute.value, ['crm-detail'])useWiseCrmConfig()
CRM initialization and configuration composable.
import { useWiseCrmConfig } from '@wisemen/wise-crm-web'
const wiseCrm = useWiseCrmConfig()Properties
isInitialized: Ref<boolean>
Reactive flag indicating if CRM has been initialized.
watch(wiseCrm.isInitialized, (initialized) => {
if (initialized) {
console.log('CRM is ready!')
}
})Methods
init(props: InitProps): Promise<void>
Initialize the CRM (see Configuration section).
getConfig(): TenantConfig
Get the current tenant configuration.
const config = wiseCrm.getConfig()
console.log(config.entities) // Entity configurationsgetOptions(): InitProps['options']
Get the initialization options.
const options = wiseCrm.getOptions()
console.log(options.deals?.allowedTransitionTriggerTypes)getCustomFieldDefinitionForEntity(entityType: EntityType): CustomFieldDefinitionIndex[]
Get custom field definitions for an entity type.
const customFields = wiseCrm.getCustomFieldDefinitionForEntity(EntityType.BUSINESS)useWiseCrmContactCreateDialog()
Composable for controlling the contact creation dialog.
import { useWiseCrmContactCreateDialog } from '@wisemen/wise-crm-web'
const contactDialog = useWiseCrmContactCreateDialog()Methods
open(businessUuid: WiseCrmBusinessUuid): void
Open the contact creation dialog for a specific business.
contactDialog.open(businessUuid)Routing
useWiseCrmRoutes(options)
Generate CRM routes with tab-based detail views.
import type { ComputedRef } from 'vue'
import { useWiseCrmRoutes } from '@wisemen/wise-crm-web'
interface CrmRouteOptions {
label: ComputedRef<string>
route: RouteRecordRaw
}
const crmRoutes = useWiseCrmRoutes({
businessTabRoutes: CrmRouteOptions[],
individualTabRoutes: CrmRouteOptions[],
dealTabRoutes: CrmRouteOptions[],
excludedReturnRoutes?: (string | RegExp)[], // Optional
})Parameters
businessTabRoutes: Array of tab routes for business detail pagesindividualTabRoutes: Array of tab routes for individual detail pagesdealTabRoutes: Array of tab routes for deal detail pagesexcludedReturnRoutes: Routes to exclude from return navigation tracking
Example
const crmRoutes = useWiseCrmRoutes({
businessTabRoutes: [
{
label: computed(() => i18n.t('information')),
route: {
name: 'crm-business-information',
path: 'information',
component: BusinessInformationView,
},
},
{
label: computed(() => i18n.t('contacts')),
route: {
name: 'crm-business-contacts',
path: 'contacts',
component: BusinessContactsView,
},
},
],
individualTabRoutes: [
{
label: computed(() => i18n.t('information')),
route: {
name: 'crm-individual-information',
path: 'information',
component: IndividualInformationView,
},
},
],
dealTabRoutes: [],
excludedReturnRoutes: [
'crm-business-detail',
'crm-individual-detail',
/^crm-.*-edit$/,
],
})Generated Routes
The composable automatically generates these route patterns:
/individual/:individualUuid- Individual detail page with tabs/business/:businessUuid- Business detail page with tabs/deal/:dealUuid- Deal detail page with tabs
Internationalization
Available Locales
en-US- English (United States)nl-BE- Dutch (Belgium)
Integration
import { wiseCrmLocales } from '@wisemen/wise-crm-web'
const i18n = createI18n({
messages: {
'en-US': {
...yourMessages,
...wiseCrmLocales['en-US'],
},
'nl-BE': {
...yourMessages,
...wiseCrmLocales['nl-BE'],
},
},
})Updating Locale
The CRM automatically watches for locale changes if you're using vue-i18n. To manually update:
import { useI18n } from 'vue-i18n'
const i18n = useI18n()
i18n.locale.value = 'nl-BE' // CRM will automatically updateIcons
Integration
import { wiseCrmIcons } from '@wisemen/wise-crm-web'
export const icons = {
...wiseCrmIcons,
// Your custom icons
customIcon: () => import('./CustomIcon.vue'),
}Available Icons
The CRM includes icons for:
- Entity types (business, individual, deal)
- Actions (add, edit, delete, search)
- Navigation (back, forward, close)
- Status indicators
- And more...
Testing Utilities
Factory Classes
The library includes factory classes for generating mock data in tests.
Import Path
import {
BusinessFactory,
IndividualFactory,
DealFactory,
NoteFactory,
} from '@wisemen/wise-crm-web/testing'BusinessFactory
Methods
createDetail(overrides?): ViewBusinessDetailResponse
Create a mock business detail object.
const business = BusinessFactory.createDetail({
name: 'Custom Corp',
email: '[email protected]',
})createIndex(overrides?): ViewBusinessIndexResponseItem
Create a mock business list item.
const businessItem = BusinessFactory.createIndex({
name: 'List Item Corp',
})createIndexList(count, overrides?): ViewBusinessIndexResponseItem[]
Create an array of mock business list items.
const businesses = BusinessFactory.createIndexList(10)createBillingInformation(overrides?): ViewBusinessBillingInformationDetailResponse
Create mock billing information.
const billing = BusinessFactory.createBillingInformation({
invoiceEmail: '[email protected]',
})createContactPerson(overrides?): BusinessContactPersonResponse
Create a mock contact person.
const contact = BusinessFactory.createContactPerson({
firstName: 'John',
lastName: 'Doe',
})createStatistics(overrides?): ViewBusinessStatisticsResponse
Create mock business statistics.
const stats = BusinessFactory.createStatistics({
total: 100,
monthlyAdded: 10,
})IndividualFactory
Similar methods to BusinessFactory for individual entities:
createDetail(overrides?)createIndex(overrides?)createIndexList(count, overrides?)createBillingInformation(overrides?)createStatistics(overrides?)
DealFactory
createDetail(overrides?)createIndex(overrides?)createIndexList(count, overrides?)
NoteFactory
create(overrides?)createList(count, overrides?)
Testing Example
import { expect, test } from '@playwright/test'
import { BusinessFactory } from '@wisemen/wise-crm-web/testing'
import { HttpResponse } from 'msw'
test('Create business', async ({ page, http, worker }) => {
const mockBusiness = BusinessFactory.createDetail({
name: 'Test Business',
})
await worker.use(
http.post('*/api/v1/businesses', () => {
return HttpResponse.json(
{ uuid: mockBusiness.uuid },
{ status: 201 }
)
}),
http.get(`*/api/v1/businesses/${mockBusiness.uuid}`, () => {
return HttpResponse.json(mockBusiness)
}),
)
await page.goto('/crm/businesses')
// ... rest of test
})Type System
Entity UUID Types
The library exports branded UUID types for type safety:
import type {
WiseCrmBusinessUuid,
WiseCrmIndividualUuid,
} from '@wisemen/wise-crm-web'
// These are branded types that prevent mixing UUIDs
function updateBusiness(uuid: WiseCrmBusinessUuid): void {
// TypeScript ensures you can't pass WiseCrmIndividualUuid here
}Validation Schemas
UUID schemas for runtime validation:
import {
businessUuidSchema,
individualUuidSchema,
} from '@wisemen/wise-crm-web'
// Use with zod or similar validators
const result = businessUuidSchema.safeParse(value)Dialog Content Tabs
import type { WiseCrmDialogContentTab } from '@wisemen/wise-crm-web'
// Type for custom dialog tab content
const tabs: WiseCrmDialogContentTab[] = [
{ label: 'General', component: GeneralTab },
{ label: 'Advanced', component: AdvancedTab },
]Environment Variables
Required
# CRM API Base URL
API_CRM_BASE_URL=https://api.your-crm.com
# Google Maps API Key (for address autocomplete)
GOOGLE_MAPS_API_KEY=your_google_maps_api_keyExample .env
API_CRM_BASE_URL=https://wise-crm.development.appwi.se
GOOGLE_MAPS_API_KEY=AIzaSyD0DOeAK9X_gXAtwPEyKPl4CaJc1dhKC9QAdvanced Usage
Custom Field Definitions
Access custom field definitions for dynamic form generation:
import { EntityType } from '@wisemen/wise-crm-web/client'
import { useWiseCrmConfig } from '@wisemen/wise-crm-web'
const crmConfig = useWiseCrmConfig()
const customFields = crmConfig.getCustomFieldDefinitionForEntity(
EntityType.BUSINESS
)
// Use customFields to render dynamic form fieldsEntity Configuration
Get localized entity names and colors:
import { EntityType } from '@wisemen/wise-crm-web/client'
import { useWiseCrm } from '@wisemen/wise-crm-web'
const crm = useWiseCrm()
const businessConfig = crm.getEntityConfig(EntityType.BUSINESS)
console.log(businessConfig?.name) // "Business" or "Bedrijf"
console.log(businessConfig?.color) // "blue", "green", etc.CSS Custom Properties
The CRM automatically sets CSS custom properties for entity colors:
/* Available CSS variables */
--wise-crm-business-50
--wise-crm-business-100
--wise-crm-business-200
/* ... etc for all color shades */
--wise-crm-individual-50
--wise-crm-individual-100
/* ... etc */Use these in your custom components for consistent theming:
<style scoped>
.business-badge {
background-color: var(--wise-crm-business-100);
color: var(--wise-crm-business-700);
}
</style>Troubleshooting
"CRM Tenant Config not initialized" Error
Cause: Attempting to use CRM components before initialization.
Solution: Ensure useWiseCrmConfig().init() is called before rendering any CRM components. Use isInitialized to conditionally render:
<script setup lang="ts">
import { useWiseCrmConfig } from '@wisemen/wise-crm-web'
const crmConfig = useWiseCrmConfig()
// Initialize in onMounted or similar
onMounted(async () => {
await crmConfig.init({ /* ... */ })
})
</script>
<template>
<div v-if="crmConfig.isInitialized.value">
<WiseCrmOverviewContent />
</div>
<div v-else>
Loading CRM...
</div>
</template>TypeScript Errors with Factory Methods
Cause: Stale TypeScript cache or build artifacts.
Solution:
- Restart TypeScript server:
Cmd+Shift+P→ "TypeScript: Restart TS Server" - Rebuild packages:
pnpm build - Clear build cache:
rm -rf dist node_modules/.cache
Address Autocomplete Not Working
Cause: Invalid or missing Google Maps API key.
Solution: Ensure your GOOGLE_MAPS_API_KEY environment variable is set and the key has the Places API enabled in Google Cloud Console.
Examples
Complete Integration Example
<!-- App.vue -->
<script setup lang="ts">
import { useWiseCrmConfig } from '@wisemen/wise-crm-web'
import { BoardColumnTransitionTriggerType } from '@wisemen/wise-crm-web/client'
import { onMounted } from 'vue'
const crmConfig = useWiseCrmConfig()
onMounted(async () => {
await crmConfig.init({
apiCrmBaseUrl: import.meta.env.API_CRM_BASE_URL,
getAccessToken: () => authClient.getToken(),
googleMapsApiKey: import.meta.env.GOOGLE_MAPS_API_KEY,
locale: 'en-US',
options: {
deals: {
allowedTransitionTriggerTypes: [
BoardColumnTransitionTriggerType.MANUAL,
],
},
widgets: {
entityStatistics: {
viewAllBusinessRouteName: 'businesses',
viewAllIndividualRouteName: 'individuals',
},
recentlyCreatedEntities: true,
},
},
})
})
</script>
<template>
<RouterView v-if="crmConfig.isInitialized.value" />
</template>// router.ts
import { createRouter } from 'vue-router'
import { useWiseCrmRoutes } from '@wisemen/wise-crm-web'
import { computed } from 'vue'
const router = createRouter({
routes: [
{
path: '/crm',
children: [
{
name: 'crm-overview',
path: '',
component: () => import('./views/CrmOverview.vue'),
},
{
name: 'crm-businesses',
path: 'businesses',
component: () => import('./views/BusinessList.vue'),
},
{
name: 'crm-individuals',
path: 'individuals',
component: () => import('./views/IndividualList.vue'),
},
],
},
useWiseCrmRoutes({
businessTabRoutes: [
{
label: computed(() => 'Information'),
route: {
name: 'business-info',
path: 'information',
component: () => import('./views/BusinessInfo.vue'),
},
},
],
individualTabRoutes: [
{
label: computed(() => 'Information'),
route: {
name: 'individual-info',
path: 'information',
component: () => import('./views/IndividualInfo.vue'),
},
},
],
dealTabRoutes: [],
}),
],
})License
MIT
Support
For issues and questions, please contact the WiseMen Digital team.
