diff --git a/api/src/database.types.ts b/api/src/database.types.ts index 779b909..fe5d92c 100644 --- a/api/src/database.types.ts +++ b/api/src/database.types.ts @@ -145,6 +145,7 @@ export type Database = { deleted_at: string | null id: string is_active: boolean + standard_name: string | null updated_at: string | null user_id: string } @@ -154,6 +155,7 @@ export type Database = { deleted_at?: string | null id?: string is_active?: boolean + standard_name?: string | null updated_at?: string | null user_id: string } @@ -163,6 +165,7 @@ export type Database = { deleted_at?: string | null id?: string is_active?: boolean + standard_name?: string | null updated_at?: string | null user_id?: string } diff --git a/sql/19_standard_name.sql b/sql/19_standard_name.sql new file mode 100644 index 0000000..a7f8cc8 --- /dev/null +++ b/sql/19_standard_name.sql @@ -0,0 +1,44 @@ +-- Add standard_name column to event_types table +-- This column should not be modifiable by authenticated users + +-- Add the standard_name column +ALTER TABLE event_types ADD COLUMN standard_name TEXT; + +-- Add comment for the new column +COMMENT ON COLUMN event_types.standard_name IS + 'Standard name for the event type - not modifiable by authenticated users'; + +-- Create function to automatically set standard_name on insert and prevent modification by authenticated users +CREATE OR REPLACE FUNCTION handle_event_types_standard_name() +RETURNS TRIGGER AS $$ +BEGIN + -- On INSERT: automatically set standard_name from config->>'name', sanitized + IF TG_OP = 'INSERT' THEN + -- Extract name from config and sanitize it (replace spaces with hyphens, lowercase) + NEW.standard_name = LOWER(REPLACE(TRIM(NEW.config->>'name'), ' ', '-')); + RETURN NEW; + END IF; + + -- On UPDATE: prevent standard_name modification by authenticated users + IF TG_OP = 'UPDATE' THEN + -- Only allow system/service role to modify standard_name + -- If the current user is authenticated (not service_role), prevent standard_name changes + IF current_setting('role') != 'service_role' AND OLD.standard_name IS DISTINCT FROM NEW.standard_name THEN RAISE EXCEPTION 'standard_name column cannot be modified'; END IF; + + -- If name in config changes, update standard_name accordingly (but only for non-authenticated users) + IF current_setting('role') = 'service_role' AND OLD.config->>'name' IS DISTINCT FROM NEW.config->>'name' THEN + NEW.standard_name = LOWER(REPLACE(TRIM(NEW.config->>'name'), ' ', '-')); + END IF; + END IF; + + RETURN NEW; +END; +$$ language 'plpgsql'; + +-- Create trigger to handle standard_name on insert and prevent modification on update +CREATE TRIGGER handle_event_types_standard_name_trigger + BEFORE INSERT OR UPDATE ON event_types + FOR EACH ROW + EXECUTE FUNCTION handle_event_types_standard_name(); + + diff --git a/ui/src/hooks/event-types.ts b/ui/src/hooks/event-types.ts index 26ba484..9bf9313 100644 --- a/ui/src/hooks/event-types.ts +++ b/ui/src/hooks/event-types.ts @@ -20,6 +20,7 @@ export type EventType = { value: number; unit: "minutes" | "hours" | "days"; }; // minimum hours in advance + standardName: string; }; const QUERY_KEY = ["event-types"]; @@ -140,6 +141,7 @@ export function useEventTypes() { ...eventTypeConfig, isActive: eventType.is_active, id: eventType.id, + standardName: eventType.standard_name, }; }) ?? [] ); diff --git a/ui/src/pages/event-types-page.tsx b/ui/src/pages/event-types-page.tsx index e3f1a92..d44bb4b 100644 --- a/ui/src/pages/event-types-page.tsx +++ b/ui/src/pages/event-types-page.tsx @@ -1,12 +1,21 @@ import { useState } from "react"; import { Strong, Text } from "@ui/ui-library/text"; import { Button, ToggleButton } from "@ui/ui-library/button"; -import { PlusIcon, EditIcon, TrashIcon, CheckIcon, XIcon } from "lucide-react"; +import { + PlusIcon, + EditIcon, + TrashIcon, + CheckIcon, + XIcon, + LinkIcon, +} from "lucide-react"; import { toast } from "@ui/ui-library/toast/toast-queue"; import { EventTypeModal } from "@ui/components/EventTypeModal"; import { EventType, useEventTypes } from "@ui/hooks/event-types"; +import { useUser } from "@ui/providers/UserStoreProvider"; export function EventTypesPage() { + const user = useUser(); const [isModalOpen, setIsModalOpen] = useState(false); const [editingEventType, setEditingEventType] = useState( null @@ -71,6 +80,23 @@ export function EventTypesPage() { setEditingEventType(null); }; + const handleOpenPublicLink = (eventType: EventType) => { + // Sanitize user name for URL (replace spaces with hyphens, lowercase, remove special chars) + const sanitizedUserName = user.name + ?.toLowerCase() + .replace(/\s+/g, "-") + .replace(/[^a-z0-9-]/g, ""); + + const shortUserId = user.id.substring(0, 6); + // Construct the public booking URL + const baseUrl = window.location.origin; + const publicUrl = `${baseUrl}/book/${sanitizedUserName}-${shortUserId}/${eventType.standardName}`; + console.log(publicUrl); + + // Open in new tab + // window.open(publicUrl, "_blank"); + }; + return (
@@ -104,6 +130,15 @@ export function EventTypesPage() {

{eventType.name}

+