Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/.yarn/** linguist-vendored
/.yarn/releases/* binary
/.yarn/plugins/**/* binary
/.pnp.* binary linguist-generated
2 changes: 0 additions & 2 deletions apps/web/modules/insights/insights-call-history-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -312,14 +312,12 @@ function CallHistoryContent({ org: _org }: CallHistoryProps) {
</>
}
EmptyView={
<div className="px-6 py-8">
<EmptyScreen
Icon="phone"
headline={searchTerm ? t("no_result_found_for", { searchTerm }) : t("no_call_history")}
description={t("no_call_history_description")}
className="mb-16"
/>
</div>
}
/>

Expand Down
2 changes: 1 addition & 1 deletion companion/extension/entrypoints/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default defineContentScript({

// Create iframe
const iframe = document.createElement("iframe");
iframe.src = "http://localhost:8081";
iframe.src = "https://companion.cal.com";
iframe.style.width = "400px";
iframe.style.height = "100%";
iframe.style.border = "none";
Expand Down
4 changes: 2 additions & 2 deletions companion/extension/public/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
"name": "Cal.com Companion",
"version": "1.7.0",
"description": "Your calendar companion for quick booking and scheduling",
"permissions": ["activeTab", "http://localhost:8081/*", "identity"],
"permissions": ["activeTab", "https://companion.cal.com/*", "identity"],
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'; frame-src 'self' http://localhost:8081;"
"extension_pages": "script-src 'self'; object-src 'self'; frame-src 'self' https://companion.cal.com;"
},
"action": {
"default_title": "Cal.com Companion"
Expand Down
4 changes: 2 additions & 2 deletions companion/wxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ export default defineConfig({
description: "Your calendar companion for quick booking and scheduling",
permissions: ["activeTab", "storage", "identity"],
host_permissions: [
"http://localhost:8081/*",
"https://companion.cal.com/*",
"https://api.cal.com/*",
"https://app.cal.com/*",
"https://mail.google.com/*",
],
content_security_policy: {
extension_pages:
"script-src 'self'; object-src 'self'; frame-src 'self' http://localhost:8081;",
"script-src 'self'; object-src 'self'; frame-src 'self' https://companion.cal.com",
},
action: {
default_title: "Cal.com Companion",
Expand Down
8 changes: 4 additions & 4 deletions packages/features/booking-audit/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -361,20 +361,20 @@ Used when a booking status changes to accepted.
#### ATTENDEE_ADDED
```typescript
{
addedAttendees // { old: null, new: ["email@example.com", ...] }
attendees // { old: ["[email protected]", ...], new: ["[email protected]", "email2@example.com", ...] }
}
```

Tracks attendee(s) that were added in this action. Old value is null since we're tracking the delta, not full state.
Tracks attendee(s) that were added in this action. The field stores the state change: `old` contains attendees before addition, `new` contains attendees after addition. The actual added attendees are computed as the difference (new - old).

#### ATTENDEE_REMOVED
```typescript
{
removedAttendees // { old: null, new: ["email@example.com", ...] }
attendees // { old: ["[email protected]", ...], new: ["email2@example.com", ...] }
}
```

Tracks attendee(s) that were removed in this action. Old value is null since we're tracking the delta, not full state.
Tracks attendee(s) that were removed in this action. The field stores the state change: `old` contains attendees before removal, `new` contains remaining attendees after removal. The actual removed attendees are computed as the difference (old - new).

---

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { z } from "zod";

import { StringChangeSchema } from "../common/changeSchemas";
import { AuditActionServiceHelper } from "./AuditActionServiceHelper";
import type { IAuditActionService, TranslationWithParams } from "./IAuditActionService";

/**
* Accepted Audit Action Service
* Handles ACCEPTED action with per-action versioning
*/

// Module-level because it is passed to IAuditActionService type outside the class scope
const fieldsSchemaV1 = z.object({
status: StringChangeSchema,
});

export class AcceptedAuditActionService
implements IAuditActionService<typeof fieldsSchemaV1, typeof fieldsSchemaV1> {
readonly VERSION = 1;
public static readonly TYPE = "ACCEPTED" as const;
private static dataSchemaV1 = z.object({
version: z.literal(1),
fields: fieldsSchemaV1,
});
private static fieldsSchemaV1 = fieldsSchemaV1;
public static readonly latestFieldsSchema = fieldsSchemaV1;
// Union of all versions
public static readonly storedDataSchema = AcceptedAuditActionService.dataSchemaV1;
// Union of all versions
public static readonly storedFieldsSchema = AcceptedAuditActionService.fieldsSchemaV1;
private helper: AuditActionServiceHelper<
typeof AcceptedAuditActionService.latestFieldsSchema,
typeof AcceptedAuditActionService.storedDataSchema
>;

constructor() {
this.helper = new AuditActionServiceHelper({
latestVersion: this.VERSION,
latestFieldsSchema: AcceptedAuditActionService.latestFieldsSchema,
storedDataSchema: AcceptedAuditActionService.storedDataSchema,
});
}

getVersionedData(fields: unknown) {
return this.helper.getVersionedData(fields);
}

parseStored(data: unknown) {
return this.helper.parseStored(data);
}

getVersion(data: unknown): number {
return this.helper.getVersion(data);
}

migrateToLatest(data: unknown) {
// V1-only: validate and return as-is (no migration needed)
const validated = fieldsSchemaV1.parse(data);
return { isMigrated: false, latestData: validated };
}

async getDisplayTitle(): Promise<TranslationWithParams> {
return { key: "booking_audit_action.accepted" };
}

getDisplayJson(storedData: { version: number; fields: z.infer<typeof fieldsSchemaV1> }): AcceptedAuditDisplayData {
const { fields } = storedData;
return {
previousStatus: fields.status.old ?? null,
newStatus: fields.status.new ?? null,
};
}
}

export type AcceptedAuditData = z.infer<typeof fieldsSchemaV1>;

export type AcceptedAuditDisplayData = {
previousStatus: string | null;
newStatus: string | null;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { z } from "zod";

import { StringArrayChangeSchema } from "../common/changeSchemas";
import { AuditActionServiceHelper } from "./AuditActionServiceHelper";
import type { IAuditActionService, TranslationWithParams } from "./IAuditActionService";

/**
* Attendee Added Audit Action Service
* Handles ATTENDEE_ADDED action with per-action versioning
*/

// Module-level because it is passed to IAuditActionService type outside the class scope
const fieldsSchemaV1 = z.object({
attendees: StringArrayChangeSchema,
});

export class AttendeeAddedAuditActionService
implements IAuditActionService<typeof fieldsSchemaV1, typeof fieldsSchemaV1> {
readonly VERSION = 1;
public static readonly TYPE = "ATTENDEE_ADDED" as const;
private static dataSchemaV1 = z.object({
version: z.literal(1),
fields: fieldsSchemaV1,
});
private static fieldsSchemaV1 = fieldsSchemaV1;
public static readonly latestFieldsSchema = fieldsSchemaV1;
// Union of all versions
public static readonly storedDataSchema = AttendeeAddedAuditActionService.dataSchemaV1;
// Union of all versions
public static readonly storedFieldsSchema = AttendeeAddedAuditActionService.fieldsSchemaV1;
private helper: AuditActionServiceHelper<
typeof AttendeeAddedAuditActionService.latestFieldsSchema,
typeof AttendeeAddedAuditActionService.storedDataSchema
>;

constructor() {
this.helper = new AuditActionServiceHelper({
latestVersion: this.VERSION,
latestFieldsSchema: AttendeeAddedAuditActionService.latestFieldsSchema,
storedDataSchema: AttendeeAddedAuditActionService.storedDataSchema,
});
}

getVersionedData(fields: unknown) {
return this.helper.getVersionedData(fields);
}

parseStored(data: unknown) {
return this.helper.parseStored(data);
}

getVersion(data: unknown): number {
return this.helper.getVersion(data);
}

migrateToLatest(data: unknown) {
// V1-only: validate and return as-is (no migration needed)
const validated = fieldsSchemaV1.parse(data);
return { isMigrated: false, latestData: validated };
}

async getDisplayTitle(): Promise<TranslationWithParams> {
return { key: "booking_audit_action.attendee_added" };
}

getDisplayJson(storedData: { version: number; fields: z.infer<typeof fieldsSchemaV1> }): AttendeeAddedAuditDisplayData {
const { fields } = storedData;
const previousAttendeesSet = new Set(fields.attendees.old ?? []);
const addedAttendees = fields.attendees.new.filter(
(email) => !previousAttendeesSet.has(email)
);
return {
addedAttendees,
};
}
}

export type AttendeeAddedAuditData = z.infer<typeof fieldsSchemaV1>;

export type AttendeeAddedAuditDisplayData = {
addedAttendees: string[];
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { z } from "zod";

import { BooleanChangeSchema } from "../common/changeSchemas";
import { AuditActionServiceHelper } from "./AuditActionServiceHelper";
import type { IAuditActionService, TranslationWithParams } from "./IAuditActionService";

/**
* Attendee No-Show Updated Audit Action Service
* Handles ATTENDEE_NO_SHOW_UPDATED action with per-action versioning
*/

// Module-level because it is passed to IAuditActionService type outside the class scope
const fieldsSchemaV1 = z.object({
noShowAttendee: BooleanChangeSchema,
});

export class AttendeeNoShowUpdatedAuditActionService
implements IAuditActionService<typeof fieldsSchemaV1, typeof fieldsSchemaV1> {
readonly VERSION = 1;
public static readonly TYPE = "ATTENDEE_NO_SHOW_UPDATED" as const;
private static dataSchemaV1 = z.object({
version: z.literal(1),
fields: fieldsSchemaV1,
});
private static fieldsSchemaV1 = fieldsSchemaV1;
public static readonly latestFieldsSchema = fieldsSchemaV1;
// Union of all versions
public static readonly storedDataSchema = AttendeeNoShowUpdatedAuditActionService.dataSchemaV1;
// Union of all versions
public static readonly storedFieldsSchema = AttendeeNoShowUpdatedAuditActionService.fieldsSchemaV1;
private helper: AuditActionServiceHelper<
typeof AttendeeNoShowUpdatedAuditActionService.latestFieldsSchema,
typeof AttendeeNoShowUpdatedAuditActionService.storedDataSchema
>;

constructor() {
this.helper = new AuditActionServiceHelper({
latestVersion: this.VERSION,
latestFieldsSchema: AttendeeNoShowUpdatedAuditActionService.latestFieldsSchema,
storedDataSchema: AttendeeNoShowUpdatedAuditActionService.storedDataSchema,
});
}

getVersionedData(fields: unknown) {
return this.helper.getVersionedData(fields);
}

parseStored(data: unknown) {
return this.helper.parseStored(data);
}

getVersion(data: unknown): number {
return this.helper.getVersion(data);
}

migrateToLatest(data: unknown) {
// V1-only: validate and return as-is (no migration needed)
const validated = fieldsSchemaV1.parse(data);
return { isMigrated: false, latestData: validated };
}

async getDisplayTitle(): Promise<TranslationWithParams> {
return { key: "booking_audit_action.attendee_no_show_updated" };
}

getDisplayJson(storedData: { version: number; fields: z.infer<typeof fieldsSchemaV1> }): AttendeeNoShowUpdatedAuditDisplayData {
const { fields } = storedData;
return {
noShowAttendee: fields.noShowAttendee.new,
previousNoShowAttendee: fields.noShowAttendee.old ?? null,
};
}
}

export type AttendeeNoShowUpdatedAuditData = z.infer<typeof fieldsSchemaV1>;

export type AttendeeNoShowUpdatedAuditDisplayData = {
noShowAttendee: boolean;
previousNoShowAttendee: boolean | null;
};
Loading
Loading