diff --git a/apps/web/src/components/organizations/OrganizationDashboard.tsx b/apps/web/src/components/organizations/OrganizationDashboard.tsx
index 6eeed6d0a2..dd85c97115 100644
--- a/apps/web/src/components/organizations/OrganizationDashboard.tsx
+++ b/apps/web/src/components/organizations/OrganizationDashboard.tsx
@@ -51,6 +51,7 @@ const addDismissedOrganization = (organizationId: string): void => {
type Props = {
organizationId: string;
+ organizationRouteIdentifier: string;
role: OrganizationRole;
topupAmount: number;
isAutoTopUpEnabled: boolean;
@@ -58,6 +59,7 @@ type Props = {
export function OrganizationDashboard({
organizationId,
+ organizationRouteIdentifier,
role,
topupAmount,
isAutoTopUpEnabled,
@@ -154,6 +156,7 @@ export function OrganizationDashboard({
) : (
)}
@@ -164,7 +167,10 @@ export function OrganizationDashboard({
-
+
{(currentRole === 'owner' || currentRole === 'billing_manager') && (
)}
diff --git a/apps/web/src/components/organizations/OrganizationInfoCard.tsx b/apps/web/src/components/organizations/OrganizationInfoCard.tsx
index 102f8ba35e..cdf0e22961 100644
--- a/apps/web/src/components/organizations/OrganizationInfoCard.tsx
+++ b/apps/web/src/components/organizations/OrganizationInfoCard.tsx
@@ -5,6 +5,8 @@ import { Button } from '@/components/ui/button';
import { BooleanBadge } from '@/components/ui/boolean-badge';
import {
useUpdateOrganizationName,
+ useSlugAvailability,
+ useUpdateOrganizationSlug,
useUpdateCompanyDomain,
useOrganizationWithMembers,
useAdminToggleCodeIndexing,
@@ -15,7 +17,7 @@ import { normalizeCompanyDomain, isValidDomain } from '@/lib/organizations/compa
import { ErrorCard } from '@/components/ErrorCard';
import { LoadingCard } from '@/components/LoadingCard';
import { AnimatedDollars } from './AnimatedDollars';
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
import {
Edit,
Check,
@@ -44,6 +46,8 @@ import { SpendingAlertsModal } from './SpendingAlertsModal';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { useExpiringCredits } from './useExpiringCredits';
import { useAdminOrganizationHierarchy } from '@/app/admin/api/organizations/hooks';
+import { getOrganizationRouteIdentifier } from '@/lib/organizations/organization-route-utils';
+import { useAdminCreditManagementPermission } from '@/app/admin/useAdminCreditManagementPermission';
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString('en-US', {
@@ -77,12 +81,35 @@ type InnerProps = {
type OrganizationHierarchySummary = {
id: string;
name: string;
+ slug: string | null;
};
type OrganizationHierarchySectionProps = {
parent: OrganizationHierarchySummary | null;
};
+function getOrganizationSlugErrorMessage(error: unknown): string {
+ if (!(error instanceof Error)) return 'Slug is not available';
+
+ try {
+ const parsed = JSON.parse(error.message) as unknown;
+ if (Array.isArray(parsed)) {
+ const firstIssue = parsed.find(
+ issue =>
+ typeof issue === 'object' &&
+ issue !== null &&
+ 'message' in issue &&
+ typeof issue.message === 'string'
+ );
+ if (firstIssue?.message) return firstIssue.message;
+ }
+ } catch {
+ // Non-JSON errors are already user-readable.
+ }
+
+ return error.message || 'Slug is not available';
+}
+
function OrganizationHierarchySection({ parent }: OrganizationHierarchySectionProps) {
if (!parent) {
return null;
@@ -96,7 +123,7 @@ function OrganizationHierarchySection({ parent }: OrganizationHierarchySectionPr
Parent organization:{' '}
{parent.name}
@@ -135,7 +162,7 @@ export function ChildOrganizationsCard({ organizationId }: { organizationId: str
{childOrganizations.map(childOrganization => (
{childOrganization.name}
@@ -160,9 +187,13 @@ function Inner(props: InnerProps) {
deleted_at,
auto_top_up_enabled,
} = info;
+ const organizationRouteIdentifier = getOrganizationRouteIdentifier(info);
const [isEditing, setIsEditing] = useState(false);
const [editedName, setEditedName] = useState(name);
+ const [isEditingSlug, setIsEditingSlug] = useState(false);
+ const [editedSlug, setEditedSlug] = useState(info.slug ?? '');
+ const [slugError, setSlugError] = useState(null);
const [isEditingDomain, setIsEditingDomain] = useState(false);
const [editedDomain, setEditedDomain] = useState(info.company_domain ?? '');
const [domainError, setDomainError] = useState(null);
@@ -173,11 +204,22 @@ function Inner(props: InnerProps) {
const [isSpendingAlertsModalOpen, setIsSpendingAlertsModalOpen] = useState(false);
const [ossSponsorshipDialogOpen, setOssSponsorshipDialogOpen] = useState(false);
const updateOrganizationName = useUpdateOrganizationName();
+ const updateOrganizationSlug = useUpdateOrganizationSlug();
const updateCompanyDomain = useUpdateCompanyDomain();
const adminToggleCodeIndexing = useAdminToggleCodeIndexing();
const updateSuppressTrialMessaging = useUpdateSuppressTrialMessaging();
const { expiringBlocks, expiring_mUsd, earliestExpiry } = useExpiringCredits(id);
+ const editedSlugAvailability = useSlugAvailability(id, editedSlug.trim(), {
+ enabled: isEditingSlug && Boolean(editedSlug.trim()) && editedSlug.trim() !== info.slug,
+ });
+ const isEditedSlugAvailable = editedSlugAvailability.data?.available ?? true;
+
+ useEffect(() => {
+ if (!isEditingSlug) {
+ setEditedSlug(info.slug ?? '');
+ }
+ }, [info.slug, isEditingSlug]);
const handleSave = async () => {
if (editedName.trim() === name) {
@@ -211,6 +253,51 @@ function Inner(props: InnerProps) {
}
};
+ const handleSlugSave = async () => {
+ setSlugError(null);
+ const nextSlug = editedSlug.trim();
+ if (!nextSlug) {
+ setSlugError('Organization slug is required');
+ return;
+ }
+
+ if (nextSlug === info.slug) {
+ setEditedSlug(info.slug ?? '');
+ setIsEditingSlug(false);
+ return;
+ }
+
+ if (!isEditedSlugAvailable) {
+ setSlugError('Not Available');
+ return;
+ }
+
+ try {
+ await updateOrganizationSlug.mutateAsync({
+ organizationId: id,
+ slug: nextSlug,
+ });
+ setIsEditingSlug(false);
+ } catch (error) {
+ console.error('Failed to update organization slug:', error);
+ setSlugError(getOrganizationSlugErrorMessage(error));
+ }
+ };
+
+ const handleSlugCancel = () => {
+ setEditedSlug(info.slug ?? '');
+ setSlugError(null);
+ setIsEditingSlug(false);
+ };
+
+ const handleSlugKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ void handleSlugSave();
+ } else if (e.key === 'Escape') {
+ handleSlugCancel();
+ }
+ };
+
const handleDomainSave = async () => {
setDomainError(null);
const normalized = normalizeCompanyDomain(editedDomain);
@@ -254,9 +341,16 @@ function Inner(props: InnerProps) {
};
const isKiloAdmin = useIsKiloAdmin();
+ const orgRole = useUserOrganizationRole();
const isAutoTopUpEnabled = useIsAutoTopUpEnabled();
const isInAdminDashboard = isKiloAdmin && showAdminControls;
const isOrgOwner = useCanManagePaymentInfo();
+ const adminCreditManagementPermission = useAdminCreditManagementPermission({
+ enabled: isKiloAdmin,
+ });
+ const canEditSlug =
+ (orgRole === 'owner' && !showAdminControls) ||
+ (isKiloAdmin && adminCreditManagementPermission.canManageCredits);
const hierarchyQuery = useAdminOrganizationHierarchy(id, isInAdminDashboard);
const handleSeatsRequirementEdit = () => {
@@ -352,6 +446,71 @@ function Inner(props: InnerProps) {
)}
+
+
+ {isEditingSlug ? (
+
+
+ {
+ setEditedSlug(e.target.value);
+ setSlugError(null);
+ }}
+ onKeyDown={handleSlugKeyDown}
+ className={`font-mono text-sm ${slugError ? 'border-red-400' : ''}`}
+ autoFocus
+ disabled={updateOrganizationSlug.isPending}
+ />
+ {editedSlug.trim() && !isEditedSlugAvailable ? (
+
+ Not Available
+
+ ) : null}
+
+
+
+ {slugError ?
{slugError}
: null}
+
+ ) : (
+
+
+ {info.slug || Not set}
+
+ {canEditSlug && (
+
+ )}
+
+ )}
+
+
+
+
{id}
+
{isEditingDomain ? (
@@ -437,7 +596,7 @@ function Inner(props: InnerProps) {
{auto_top_up_enabled && isAutoTopUpEnabled && (
Auto Top-up: On
@@ -447,7 +606,9 @@ function Inner(props: InnerProps) {
{expiringBlocks.length > 0 && earliestExpiry && (
@@ -465,7 +626,7 @@ function Inner(props: InnerProps) {
{readonly ? 'View ' : 'Configure '} providers and models
diff --git a/apps/web/src/components/organizations/OrganizationUsageSummaryCard.tsx b/apps/web/src/components/organizations/OrganizationUsageSummaryCard.tsx
index dd5969c0bb..cba12530af 100644
--- a/apps/web/src/components/organizations/OrganizationUsageSummaryCard.tsx
+++ b/apps/web/src/components/organizations/OrganizationUsageSummaryCard.tsx
@@ -62,7 +62,13 @@ function MetricWithTooltip({
);
}
-export function OrganizationUsageSummaryCard({ organizationId }: { organizationId: string }) {
+export function OrganizationUsageSummaryCard({
+ organizationId,
+ organizationRouteIdentifier,
+}: {
+ organizationId: string;
+ organizationRouteIdentifier?: string;
+}) {
const {
error,
data: usage_stats,
@@ -108,7 +114,6 @@ export function OrganizationUsageSummaryCard({ organizationId }: { organizationI
const averageRequestsPerDay = Math.round((usage_stats.totalRequestCount / 30) * 10) / 10;
const averageInputTokensPerDay = Math.round((usage_stats.totalInputTokens / 30) * 10) / 10;
const averageOutputTokensPerDay = Math.round((usage_stats.totalOutputTokens / 30) * 10) / 10;
-
return (
@@ -190,15 +195,17 @@ export function OrganizationUsageSummaryCard({ organizationId }: { organizationI
/>
-
+ {organizationRouteIdentifier ? (
+
+ ) : null}