Finish with url copying
This commit is contained in:
parent
27dc530b1c
commit
8d9c7332b3
6 changed files with 91 additions and 1 deletions
|
|
@ -145,6 +145,7 @@ export type Database = {
|
||||||
deleted_at: string | null
|
deleted_at: string | null
|
||||||
id: string
|
id: string
|
||||||
is_active: boolean
|
is_active: boolean
|
||||||
|
standard_name: string | null
|
||||||
updated_at: string | null
|
updated_at: string | null
|
||||||
user_id: string
|
user_id: string
|
||||||
}
|
}
|
||||||
|
|
@ -154,6 +155,7 @@ export type Database = {
|
||||||
deleted_at?: string | null
|
deleted_at?: string | null
|
||||||
id?: string
|
id?: string
|
||||||
is_active?: boolean
|
is_active?: boolean
|
||||||
|
standard_name?: string | null
|
||||||
updated_at?: string | null
|
updated_at?: string | null
|
||||||
user_id: string
|
user_id: string
|
||||||
}
|
}
|
||||||
|
|
@ -163,6 +165,7 @@ export type Database = {
|
||||||
deleted_at?: string | null
|
deleted_at?: string | null
|
||||||
id?: string
|
id?: string
|
||||||
is_active?: boolean
|
is_active?: boolean
|
||||||
|
standard_name?: string | null
|
||||||
updated_at?: string | null
|
updated_at?: string | null
|
||||||
user_id?: string
|
user_id?: string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
44
sql/19_standard_name.sql
Normal file
44
sql/19_standard_name.sql
Normal file
|
|
@ -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();
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -20,6 +20,7 @@ export type EventType = {
|
||||||
value: number;
|
value: number;
|
||||||
unit: "minutes" | "hours" | "days";
|
unit: "minutes" | "hours" | "days";
|
||||||
}; // minimum hours in advance
|
}; // minimum hours in advance
|
||||||
|
standardName: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const QUERY_KEY = ["event-types"];
|
const QUERY_KEY = ["event-types"];
|
||||||
|
|
@ -140,6 +141,7 @@ export function useEventTypes() {
|
||||||
...eventTypeConfig,
|
...eventTypeConfig,
|
||||||
isActive: eventType.is_active,
|
isActive: eventType.is_active,
|
||||||
id: eventType.id,
|
id: eventType.id,
|
||||||
|
standardName: eventType.standard_name,
|
||||||
};
|
};
|
||||||
}) ?? []
|
}) ?? []
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,21 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Strong, Text } from "@ui/ui-library/text";
|
import { Strong, Text } from "@ui/ui-library/text";
|
||||||
import { Button, ToggleButton } from "@ui/ui-library/button";
|
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 { toast } from "@ui/ui-library/toast/toast-queue";
|
||||||
import { EventTypeModal } from "@ui/components/EventTypeModal";
|
import { EventTypeModal } from "@ui/components/EventTypeModal";
|
||||||
import { EventType, useEventTypes } from "@ui/hooks/event-types";
|
import { EventType, useEventTypes } from "@ui/hooks/event-types";
|
||||||
|
import { useUser } from "@ui/providers/UserStoreProvider";
|
||||||
|
|
||||||
export function EventTypesPage() {
|
export function EventTypesPage() {
|
||||||
|
const user = useUser();
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [editingEventType, setEditingEventType] = useState<EventType | null>(
|
const [editingEventType, setEditingEventType] = useState<EventType | null>(
|
||||||
null
|
null
|
||||||
|
|
@ -71,6 +80,23 @@ export function EventTypesPage() {
|
||||||
setEditingEventType(null);
|
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 (
|
return (
|
||||||
<div className="h-full flex flex-col p-4">
|
<div className="h-full flex flex-col p-4">
|
||||||
<div className="flex justify-between items-start mb-6">
|
<div className="flex justify-between items-start mb-6">
|
||||||
|
|
@ -104,6 +130,15 @@ export function EventTypesPage() {
|
||||||
<h3 className="text-lg font-semibold">{eventType.name}</h3>
|
<h3 className="text-lg font-semibold">{eventType.name}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant="plain"
|
||||||
|
isIconOnly
|
||||||
|
onPress={() => handleOpenPublicLink(eventType as EventType)}
|
||||||
|
className="text-gray-500 hover:text-green-600"
|
||||||
|
tooltip="Ouvrir le lien de réservation public"
|
||||||
|
>
|
||||||
|
<LinkIcon className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="plain"
|
variant="plain"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,7 @@ export type Database = {
|
||||||
deleted_at: string | null
|
deleted_at: string | null
|
||||||
id: string
|
id: string
|
||||||
is_active: boolean
|
is_active: boolean
|
||||||
|
standard_name: string | null
|
||||||
updated_at: string | null
|
updated_at: string | null
|
||||||
user_id: string
|
user_id: string
|
||||||
}
|
}
|
||||||
|
|
@ -154,6 +155,7 @@ export type Database = {
|
||||||
deleted_at?: string | null
|
deleted_at?: string | null
|
||||||
id?: string
|
id?: string
|
||||||
is_active?: boolean
|
is_active?: boolean
|
||||||
|
standard_name?: string | null
|
||||||
updated_at?: string | null
|
updated_at?: string | null
|
||||||
user_id: string
|
user_id: string
|
||||||
}
|
}
|
||||||
|
|
@ -163,6 +165,7 @@ export type Database = {
|
||||||
deleted_at?: string | null
|
deleted_at?: string | null
|
||||||
id?: string
|
id?: string
|
||||||
is_active?: boolean
|
is_active?: boolean
|
||||||
|
standard_name?: string | null
|
||||||
updated_at?: string | null
|
updated_at?: string | null
|
||||||
user_id?: string
|
user_id?: string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,7 @@ export type Database = {
|
||||||
deleted_at: string | null
|
deleted_at: string | null
|
||||||
id: string
|
id: string
|
||||||
is_active: boolean
|
is_active: boolean
|
||||||
|
standard_name: string | null
|
||||||
updated_at: string | null
|
updated_at: string | null
|
||||||
user_id: string
|
user_id: string
|
||||||
}
|
}
|
||||||
|
|
@ -154,6 +155,7 @@ export type Database = {
|
||||||
deleted_at?: string | null
|
deleted_at?: string | null
|
||||||
id?: string
|
id?: string
|
||||||
is_active?: boolean
|
is_active?: boolean
|
||||||
|
standard_name?: string | null
|
||||||
updated_at?: string | null
|
updated_at?: string | null
|
||||||
user_id: string
|
user_id: string
|
||||||
}
|
}
|
||||||
|
|
@ -163,6 +165,7 @@ export type Database = {
|
||||||
deleted_at?: string | null
|
deleted_at?: string | null
|
||||||
id?: string
|
id?: string
|
||||||
is_active?: boolean
|
is_active?: boolean
|
||||||
|
standard_name?: string | null
|
||||||
updated_at?: string | null
|
updated_at?: string | null
|
||||||
user_id?: string
|
user_id?: string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue