@collabdt/core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -21
- package/dist/core/components/AppSidebarContent.d.ts.map +1 -1
- package/dist/core/components/AppSidebarContent.js +21 -27
- package/dist/core/components/AppSidebarContent.js.map +1 -1
- package/dist/core/components/authentication/AuthPage.d.ts.map +1 -1
- package/dist/core/components/authentication/AuthPage.js +32 -7
- package/dist/core/components/authentication/AuthPage.js.map +1 -1
- package/dist/core/components/authentication/SignInAuthHeader.d.ts +7 -0
- package/dist/core/components/authentication/SignInAuthHeader.d.ts.map +1 -0
- package/dist/core/components/authentication/SignInAuthHeader.js +32 -0
- package/dist/core/components/authentication/SignInAuthHeader.js.map +1 -0
- package/dist/core/components/authentication/Signin.d.ts.map +1 -1
- package/dist/core/components/authentication/Signin.js +308 -10
- package/dist/core/components/authentication/Signin.js.map +1 -1
- package/dist/core/components/settings/index.js +3 -3
- package/dist/core/components/settings/index.js.map +1 -1
- package/dist/core/components/settings/src/OrganizationSettingsPanel.d.ts.map +1 -1
- package/dist/core/components/settings/src/OrganizationSettingsPanel.js +5 -4
- package/dist/core/components/settings/src/OrganizationSettingsPanel.js.map +1 -1
- package/dist/core/components/settings/src/SettingsSidebar.d.ts.map +1 -1
- package/dist/core/components/settings/src/SettingsSidebar.js +12 -2
- package/dist/core/components/settings/src/SettingsSidebar.js.map +1 -1
- package/dist/core/components/support/BugReportDialog.d.ts.map +1 -1
- package/dist/core/components/support/BugReportDialog.js +94 -13
- package/dist/core/components/support/BugReportDialog.js.map +1 -1
- package/dist/core/components/support/FeatureRequestDialog.d.ts.map +1 -1
- package/dist/core/components/support/FeatureRequestDialog.js +33 -6
- package/dist/core/components/support/FeatureRequestDialog.js.map +1 -1
- package/dist/core/components/ui/AppSidebar.d.ts.map +1 -1
- package/dist/core/components/ui/AppSidebar.js +52 -9
- package/dist/core/components/ui/AppSidebar.js.map +1 -1
- package/dist/core/components/ui/DataTable.js +3 -3
- package/dist/core/components/ui/DataTable.js.map +1 -1
- package/dist/core/components/ui/Dialog.js +1 -1
- package/dist/core/components/ui/Dialog.js.map +1 -1
- package/dist/core/components/ui/InfoSidebar/Container.js +1 -1
- package/dist/core/components/ui/InfoSidebar/Container.js.map +1 -1
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.js +2 -2
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.js.map +1 -1
- package/dist/core/components/ui/Sensors/SensorsSection.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/SensorsSection.js +2 -1
- package/dist/core/components/ui/Sensors/SensorsSection.js.map +1 -1
- package/dist/core/components/ui/Sensors/sensorUtils.d.ts +9 -0
- package/dist/core/components/ui/Sensors/sensorUtils.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/sensorUtils.js +6 -0
- package/dist/core/components/ui/Sensors/sensorUtils.js.map +1 -1
- package/dist/core/components/ui/Sidebar.d.ts +4 -0
- package/dist/core/components/ui/Sidebar.d.ts.map +1 -1
- package/dist/core/components/ui/Sidebar.js +11 -5
- package/dist/core/components/ui/Sidebar.js.map +1 -1
- package/dist/core/components/ui/Table.js +2 -2
- package/dist/core/components/ui/Table.js.map +1 -1
- package/dist/core/components/viewers/Data/DataMenu.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/DataMenu.js +8 -8
- package/dist/core/components/viewers/Data/DataMenu.js.map +1 -1
- package/dist/core/components/viewers/Data/buildingDetails/BuildingDetails.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/buildingDetails/BuildingDetails.js +26 -0
- package/dist/core/components/viewers/Data/buildingDetails/BuildingDetails.js.map +1 -1
- package/dist/core/components/viewers/Data/details/DetailHeader.js +14 -14
- package/dist/core/components/viewers/Data/details/DetailHeader.js.map +1 -1
- package/dist/core/components/viewers/Data/details/TabSidebar.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/details/TabSidebar.js +22 -0
- package/dist/core/components/viewers/Data/details/TabSidebar.js.map +1 -1
- package/dist/core/components/viewers/Data/infrastructureDetails/InfrastructureDetails.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/infrastructureDetails/InfrastructureDetails.js +19 -0
- package/dist/core/components/viewers/Data/infrastructureDetails/InfrastructureDetails.js.map +1 -1
- package/dist/core/components/viewers/Data/siteDetails/SiteDetails.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/siteDetails/SiteDetails.js +19 -0
- package/dist/core/components/viewers/Data/siteDetails/SiteDetails.js.map +1 -1
- package/dist/core/components/viewers/Data/utils/Columns.js +2 -2
- package/dist/core/components/viewers/Data/utils/Columns.js.map +1 -1
- package/dist/core/components/viewers/Data/utils/positionParser.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/utils/positionParser.js +6 -5
- package/dist/core/components/viewers/Data/utils/positionParser.js.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.js +8 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.js.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.js +7 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/RowActions.js +6 -6
- package/dist/core/components/viewers/map/datasets/RowActions.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/data.js +1 -1
- package/dist/core/components/viewers/map/datasets/data.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/index.d.ts.map +1 -1
- package/dist/core/components/viewers/map/datasets/index.js +28 -9
- package/dist/core/components/viewers/map/datasets/index.js.map +1 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudLoadingState/index.d.ts.map +1 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudLoadingState/index.js +54 -31
- package/dist/core/components/viewers/pointcloud/src/PointCloudLoadingState/index.js.map +1 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/index.js +1 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/index.js.map +1 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/src/PointCloudSection.d.ts +2 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/src/PointCloudSection.d.ts.map +1 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/src/PointCloudSection.js +5 -4
- package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/src/PointCloudSection.js.map +1 -1
- package/dist/core/hooks/useCaptureScreenshot.d.ts.map +1 -1
- package/dist/core/hooks/useCaptureScreenshot.js +62 -31
- package/dist/core/hooks/useCaptureScreenshot.js.map +1 -1
- package/dist/core/hooks/useCurrentViewerPosition.d.ts +18 -0
- package/dist/core/hooks/useCurrentViewerPosition.d.ts.map +1 -0
- package/dist/core/hooks/useCurrentViewerPosition.js +66 -0
- package/dist/core/hooks/useCurrentViewerPosition.js.map +1 -0
- package/dist/core/hooks/useShareUrl.d.ts +2 -0
- package/dist/core/hooks/useShareUrl.d.ts.map +1 -0
- package/dist/core/hooks/useShareUrl.js +74 -0
- package/dist/core/hooks/useShareUrl.js.map +1 -0
- package/dist/core/i18n/index.d.ts +8 -0
- package/dist/core/i18n/index.d.ts.map +1 -0
- package/dist/core/i18n/index.js +8 -0
- package/dist/core/i18n/index.js.map +1 -0
- package/dist/core/i18n/messages/en.json +1873 -0
- package/dist/core/i18n/messages/es.json +1870 -0
- package/dist/core/i18n/messages/fr.json +1870 -0
- package/dist/core/styles/globals.css +46 -33
- package/package.json +9 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/core/components/settings/src/OrganizationSettingsPanel.tsx"],"sourcesContent":["\"use client\"\r\n\r\n// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport * as React from 'react'\r\nimport { useSession } from 'next-auth/react'\r\nimport { toast } from 'sonner'\r\nimport { mutate } from 'swr'\r\nimport { useTranslations } from 'next-intl'\r\nimport { MapContext, usePermissions } from '../../../store'\r\nimport * as LR from 'lucide-react'\r\n\r\nimport type { Organization } from '../../../types/dbTypes'\r\nimport { Button } from '../../ui/Button'\r\nimport { Input } from '../../ui/Input'\r\nimport { Textarea } from '../../ui/Textarea'\r\nimport { Separator } from '../../ui/Separator'\r\nimport {\r\n Select,\r\n SelectContent,\r\n SelectItem,\r\n SelectTrigger,\r\n SelectValue,\r\n} from '../../ui/Select'\r\nimport { uploadOrganizationLogoToPublicBucket } from '../../../utils/imageUtils'\r\nimport { useOrganization } from '../../../hooks/organizations/organizations'\r\nimport OrganizationSkeleton from './OrganizationSkeleton'\r\n\r\n//import { useAppConfigContext } from '../../../store/AppConfig/context'\r\n\r\ninterface OrganizationSettingsPanelProps {\r\n minioBaseUrl?: string\r\n}\r\n\r\nexport default function OrganizationSettingsPanel({\r\n minioBaseUrl,\r\n}: OrganizationSettingsPanelProps) {\r\n const t = useTranslations('OrganizationSettings')\r\n\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n const { data: session } = useSession()\r\n\r\n const {state: mapState} = React.useContext(MapContext)\r\n const { countrySubdivisionsData } = mapState.map;\r\n\r\n const user = session?.user\r\n const userOrganizationId = user?.organizationId\r\n const {\r\n organization: organizationData,\r\n isLoading: isOrganizationLoading,\r\n } = useOrganization(userOrganizationId ? String(userOrganizationId) : null)\r\n\r\n const [organization, setOrganization] = React.useState<Organization | null>(organizationData ?? null)\r\n const [editingValues, setEditingValues] = React.useState<Partial<Organization>>({})\r\n const [isEditing, setIsEditing] = React.useState(false)\r\n const [hasChanges, setHasChanges] = React.useState(false)\r\n const [selectedLogoFile, setSelectedLogoFile] = React.useState<File | null>(null)\r\n const [selectedFaviconFile, setSelectedFaviconFile] = React.useState<File | null>(null)\r\n\r\n React.useEffect(() => {\r\n if (organizationData) {\r\n setOrganization(organizationData)\r\n }\r\n }, [organizationData])\r\n\r\n const handleInputChange = (key: keyof Organization, value: any) => {\r\n setEditingValues(prev => ({\r\n ...prev,\r\n [key]: value,\r\n }))\r\n setHasChanges(true)\r\n }\r\n\r\n const getFieldValue = (property: keyof Organization) => {\r\n return Object.prototype.hasOwnProperty.call(editingValues, property)\r\n ? editingValues[property]\r\n : organization?.[property]\r\n }\r\n\r\n const handleSave = async () => {\r\n if (!organization) return\r\n\r\n try {\r\n const patch: Partial<Organization> = {\r\n ...editingValues,\r\n }\r\n\r\n // Upload logo if changed\r\n if (selectedLogoFile) {\r\n const logoKey = await uploadOrganizationLogoToPublicBucket(selectedLogoFile)\r\n patch.logoKey = logoKey\r\n }\r\n\r\n // Upload favicon if changed\r\n if (selectedFaviconFile) {\r\n const faviconKey = await uploadOrganizationLogoToPublicBucket(selectedFaviconFile)\r\n patch.faviconKey = faviconKey\r\n }\r\n\r\n const res = await fetch(`/api/organizations/${organization.id}`, {\r\n method: 'PATCH',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify(patch),\r\n })\r\n\r\n if (!res.ok) throw new Error('Failed to update organization')\r\n\r\n const responseData = await res.json()\r\n const updatedOrg = responseData?.organization ?? responseData\r\n setOrganization(updatedOrg)\r\n mutate(['organization', String(userOrganizationId)], updatedOrg, { revalidate: false })\r\n setEditingValues({})\r\n setIsEditing(false)\r\n setHasChanges(false)\r\n setSelectedLogoFile(null)\r\n setSelectedFaviconFile(null)\r\n toast.success('Organization updated successfully')\r\n } catch (error) {\r\n toast.error('Failed to update organization')\r\n }\r\n }\r\n\r\n const handleCancel = () => {\r\n setEditingValues({})\r\n setIsEditing(false)\r\n setHasChanges(false)\r\n setSelectedLogoFile(null)\r\n setSelectedFaviconFile(null)\r\n }\r\n\r\n //const { state: { runtimeConfig: { minioUrl } } } = useAppConfigContext()\r\n\r\n const logoUrl = selectedLogoFile\r\n ? URL.createObjectURL(selectedLogoFile)\r\n : (getFieldValue('logoKey') as string | null | undefined) && minioBaseUrl\r\n ? `${minioBaseUrl}/org-logos/${getFieldValue('logoKey')}`\r\n : null\r\n\r\n const faviconUrl = selectedFaviconFile\r\n ? URL.createObjectURL(selectedFaviconFile)\r\n : (getFieldValue('faviconKey') as string | null | undefined) && minioBaseUrl\r\n ? `${minioBaseUrl}/org-logos/${getFieldValue('faviconKey')}`\r\n : null\r\n\r\n// const faviconUrl = selectedFaviconFile\r\n// ? URL.createObjectURL(selectedFaviconFile)\r\n// : organization?.faviconKey && minioUrl\r\n// ? `${minioUrl}/org-logos/${organization.faviconKey}`\r\n// : null\r\n \r\n if (isOrganizationLoading) {\r\n return (\r\n <OrganizationSkeleton />\r\n )\r\n }\r\n\r\n if (!organization) {\r\n return (\r\n <div className=\"flex items-center justify-center h-full\">\r\n <p className=\"text-muted-foreground\">{t('messages.noOrganization')}</p>\r\n </div>\r\n )\r\n }\r\n\r\n const countrySubdivisions = \r\n countrySubdivisionsData\r\n ? Object.values(countrySubdivisionsData).map((subdivision: any) => subdivision.name)\r\n : []\r\n\r\n return (\r\n <div className=\"flex flex-col h-full overflow-hidden\">\r\n {/* Header */}\r\n <div className=\"flex justify-between items-center px-6 py-4 border-b\">\r\n <div>\r\n <h2 className=\"text-2xl font-semibold\">{t('header')}</h2>\r\n <p className=\"text-sm text-muted-foreground\">{t('subheader')}</p>\r\n </div>\r\n <div className=\"flex gap-2\">\r\n {isEditing ? (\r\n <>\r\n <Button variant=\"outline\" onClick={handleCancel} disabled={!ability.can('update', \"Organization\")}>\r\n {t('cancel')}\r\n </Button>\r\n <Button onClick={handleSave} disabled={!hasChanges || !ability.can('update', \"Organization\")}>\r\n <LR.Save className=\"mr-2\" />\r\n {t('save')}\r\n </Button>\r\n </>\r\n ) : (\r\n <Button onClick={() => setIsEditing(true)} disabled={!ability.can('update', \"Organization\")}>\r\n <LR.Pencil className=\"mr-2\" />\r\n {t('edit')}\r\n </Button>\r\n )}\r\n </div>\r\n </div>\r\n\r\n {/* Content */}\r\n <div className=\"flex-1 overflow-auto p-6\">\r\n <div className=\"max-w-4xl mx-auto space-y-8\">\r\n {/* Branding Section */}\r\n <div className=\"space-y-4\">\r\n <h3 className=\"text-lg font-semibold\">{t('sections.branding')}</h3>\r\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\r\n {/* Logo */}\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.logo')}</label>\r\n <div className=\"flex items-center gap-4\">\r\n <div className=\"w-32 h-32 border rounded-lg overflow-hidden bg-muted flex items-center justify-center relative group\">\r\n {logoUrl ? (\r\n <img src={logoUrl} alt=\"Organization logo\" className=\"w-full h-full object-contain\" />\r\n ) : (\r\n <LR.Building2 className=\"h-12 w-12 text-muted-foreground\" />\r\n )}\r\n {isEditing && (\r\n <label className=\"absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center cursor-pointer\">\r\n <LR.Upload className=\"h-6 w-6 text-white\" />\r\n <input\r\n type=\"file\"\r\n accept=\"image/*\"\r\n className=\"hidden\"\r\n onChange={(e) => {\r\n const file = e.target.files?.[0]\r\n if (file) {\r\n setSelectedLogoFile(file)\r\n setHasChanges(true)\r\n }\r\n }}\r\n disabled={!ability.can('update', \"Organization\")}\r\n />\r\n </label>\r\n )}\r\n </div>\r\n {isEditing && logoUrl && (\r\n <Button\r\n variant=\"outline\"\r\n size=\"sm\"\r\n onClick={() => {\r\n setSelectedLogoFile(null)\r\n handleInputChange('logoKey', null)\r\n }}\r\n disabled={!ability.can('update', \"Organization\")}\r\n >\r\n Remove\r\n </Button>\r\n )}\r\n </div>\r\n </div>\r\n\r\n {/* Favicon */}\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.favicon')}</label>\r\n <div className=\"flex items-center gap-4\">\r\n <div className=\"w-16 h-16 border rounded-lg overflow-hidden bg-muted flex items-center justify-center relative group\">\r\n {faviconUrl ? (\r\n <img src={faviconUrl} alt=\"Favicon\" className=\"w-full h-full object-contain\" />\r\n ) : (\r\n <LR.Image className=\"h-6 w-6 text-muted-foreground\" />\r\n )}\r\n {isEditing && (\r\n <label className=\"absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center cursor-pointer\">\r\n <LR.Upload className=\"h-4 w-4 text-white\" />\r\n <input\r\n type=\"file\"\r\n accept=\"image/*\"\r\n className=\"hidden\"\r\n onChange={(e) => {\r\n const file = e.target.files?.[0]\r\n if (file) {\r\n setSelectedFaviconFile(file)\r\n setHasChanges(true)\r\n }\r\n }}\r\n disabled={!ability.can('update', \"Organization\")}\r\n />\r\n </label>\r\n )}\r\n </div>\r\n {isEditing && faviconUrl && (\r\n <Button\r\n variant=\"outline\"\r\n size=\"sm\"\r\n onClick={() => {\r\n setSelectedFaviconFile(null)\r\n handleInputChange('faviconKey', null)\r\n }}\r\n disabled={!ability.can('update', \"Organization\")}\r\n >\r\n Remove\r\n </Button>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {/* Colors */}\r\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.mainColor')}</label>\r\n <div className=\"flex gap-2\">\r\n <Input\r\n type=\"color\"\r\n value={(getFieldValue('mainColor') as string) || '#000000'}\r\n onChange={(e) => handleInputChange('mainColor', e.target.value)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n className=\"w-20 h-10 p-1\"\r\n \r\n />\r\n <Input\r\n type=\"text\"\r\n value={(getFieldValue('mainColor') as string) || ''}\r\n onChange={(e) => handleInputChange('mainColor', e.target.value)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n placeholder=\"#000000\"\r\n />\r\n </div>\r\n </div>\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.secondaryColor')}</label>\r\n <div className=\"flex gap-2\">\r\n <Input\r\n type=\"color\"\r\n value={(getFieldValue('secondaryColor') as string) || '#000000'}\r\n onChange={(e) => handleInputChange('secondaryColor', e.target.value)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n className=\"w-20 h-10 p-1\"\r\n />\r\n <Input\r\n type=\"text\"\r\n value={(getFieldValue('secondaryColor') as string) || ''}\r\n onChange={(e) => handleInputChange('secondaryColor', e.target.value)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n placeholder=\"#000000\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <Separator />\r\n\r\n {/* Basic Information */}\r\n <div className=\"space-y-4\">\r\n <h3 className=\"text-lg font-semibold\">{t('sections.basicInfo')}</h3>\r\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.name')}</label>\r\n <Input\r\n value={(getFieldValue('name') as string) || ''}\r\n onChange={(e) => handleInputChange('name', e.target.value)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n />\r\n </div>\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.title')}</label>\r\n <Input\r\n value={(getFieldValue('title') as string) || ''}\r\n onChange={(e) => handleInputChange('title', e.target.value)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\") }\r\n />\r\n </div>\r\n </div>\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.description')}</label>\r\n <Textarea\r\n value={(getFieldValue('description') as string) || ''}\r\n onChange={(e) => handleInputChange('description', e.target.value)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n rows={4}\r\n />\r\n </div>\r\n </div>\r\n\r\n <Separator />\r\n\r\n {/* Location */}\r\n <div className=\"space-y-4\">\r\n <h3 className=\"text-lg font-semibold\">{t('sections.location')}</h3>\r\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.countrySubdivision')}</label>\r\n <Select\r\n value={String(getFieldValue('countrySubdivision') || '')}\r\n onValueChange={(value) => handleInputChange('countrySubdivision', value)}\r\n disabled={!isEditing}\r\n >\r\n <SelectTrigger>\r\n <SelectValue placeholder={t('placeholders.selectProvince')} />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {countrySubdivisions.map((csName) => (\r\n <SelectItem key={csName} value={csName}>\r\n {csName}\r\n </SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n </div>\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.municipality')}</label>\r\n <Input\r\n value={(getFieldValue('municipality') as string) || ''}\r\n onChange={(e) => handleInputChange('municipality', e.target.value)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n />\r\n </div>\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.postalCode')}</label>\r\n <Input\r\n value={(getFieldValue('postalCode') as string) || ''}\r\n onChange={(e) => handleInputChange('postalCode', e.target.value)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n />\r\n </div>\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.locationId')}</label>\r\n <Input\r\n value={(getFieldValue('locationId') as string) || ''}\r\n onChange={(e) => handleInputChange('locationId', e.target.value)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <Separator />\r\n\r\n {/* Map Configuration */}\r\n <div className=\"space-y-4\">\r\n <h3 className=\"text-lg font-semibold\">{t('sections.mapConfig')}</h3>\r\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4\">\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.latitude')}</label>\r\n <Input\r\n type=\"number\"\r\n step=\"any\"\r\n value={(getFieldValue('lat') as number) ?? ''}\r\n onChange={(e) => handleInputChange('lat', e.target.value ? parseFloat(e.target.value) : null)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n />\r\n </div>\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.longitude')}</label>\r\n <Input\r\n type=\"number\"\r\n step=\"any\"\r\n value={(getFieldValue('long') as number) ?? ''}\r\n onChange={(e) => handleInputChange('long', e.target.value ? parseFloat(e.target.value) : null)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n />\r\n </div>\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.zoom')}</label>\r\n <Input\r\n type=\"number\"\r\n step=\"any\"\r\n value={(getFieldValue('zoom') as number) ?? ''}\r\n onChange={(e) => handleInputChange('zoom', e.target.value ? parseFloat(e.target.value) : null)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n />\r\n </div>\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.minZoom')}</label>\r\n <Input\r\n type=\"number\"\r\n step=\"any\"\r\n value={(getFieldValue('minZoom') as number) ?? ''}\r\n onChange={(e) => handleInputChange('minZoom', e.target.value ? parseFloat(e.target.value) : null)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n />\r\n </div>\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.bearing')}</label>\r\n <Input\r\n type=\"number\"\r\n step=\"any\"\r\n value={(getFieldValue('bearing') as number) ?? ''}\r\n onChange={(e) => handleInputChange('bearing', e.target.value ? parseFloat(e.target.value) : null)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n />\r\n </div>\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.pitch')}</label>\r\n <Input\r\n type=\"number\"\r\n step=\"any\"\r\n value={(getFieldValue('pitch') as number) ?? ''}\r\n onChange={(e) => handleInputChange('pitch', e.target.value ? parseFloat(e.target.value) : null)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n )\r\n}\r\n"],"mappings":"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","names":["_a"]}
|
|
1
|
+
{"version":3,"sources":["../../../../../src/core/components/settings/src/OrganizationSettingsPanel.tsx"],"sourcesContent":["\"use client\"\r\n\r\n// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport * as React from 'react'\r\nimport { useSession } from 'next-auth/react'\r\nimport { toast } from 'sonner'\r\nimport { mutate } from 'swr'\r\nimport { useTranslations } from 'next-intl'\r\nimport { MapContext, usePermissions } from '../../../store'\r\nimport * as LR from 'lucide-react'\r\n\r\nimport type { Organization } from '../../../types/dbTypes'\r\nimport { Button } from '../../ui/Button'\r\nimport { Input } from '../../ui/Input'\r\nimport { Textarea } from '../../ui/Textarea'\r\nimport { Separator } from '../../ui/Separator'\r\nimport {\r\n Select,\r\n SelectContent,\r\n SelectItem,\r\n SelectTrigger,\r\n SelectValue,\r\n} from '../../ui/Select'\r\nimport { uploadOrganizationLogoToPublicBucket } from '../../../utils/imageUtils'\r\nimport { useOrganization } from '../../../hooks/organizations/organizations'\r\nimport OrganizationSkeleton from './OrganizationSkeleton'\r\n\r\n//import { useAppConfigContext } from '../../../store/AppConfig/context'\r\n\r\ninterface OrganizationSettingsPanelProps {\r\n minioBaseUrl?: string\r\n}\r\n\r\nexport default function OrganizationSettingsPanel({\r\n minioBaseUrl,\r\n}: OrganizationSettingsPanelProps) {\r\n const t = useTranslations('OrganizationSettings')\r\n\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n const { data: session } = useSession()\r\n\r\n const {state: mapState} = React.useContext(MapContext)\r\n const { countrySubdivisionsData } = mapState.map;\r\n\r\n const user = session?.user\r\n const userOrganizationId = user?.organizationId\r\n const {\r\n organization: organizationData,\r\n isLoading: isOrganizationLoading,\r\n } = useOrganization(userOrganizationId ? String(userOrganizationId) : null)\r\n\r\n const [organization, setOrganization] = React.useState<Organization | null>(organizationData ?? null)\r\n const [editingValues, setEditingValues] = React.useState<Partial<Organization>>({})\r\n const [isEditing, setIsEditing] = React.useState(false)\r\n const [hasChanges, setHasChanges] = React.useState(false)\r\n const [selectedLogoFile, setSelectedLogoFile] = React.useState<File | null>(null)\r\n const [selectedFaviconFile, setSelectedFaviconFile] = React.useState<File | null>(null)\r\n\r\n React.useEffect(() => {\r\n if (organizationData) {\r\n setOrganization(organizationData)\r\n }\r\n }, [organizationData])\r\n\r\n const handleInputChange = (key: keyof Organization, value: any) => {\r\n setEditingValues(prev => ({\r\n ...prev,\r\n [key]: value,\r\n }))\r\n setHasChanges(true)\r\n }\r\n\r\n const getFieldValue = (property: keyof Organization) => {\r\n return Object.prototype.hasOwnProperty.call(editingValues, property)\r\n ? editingValues[property]\r\n : organization?.[property]\r\n }\r\n\r\n const handleSave = async () => {\r\n if (!organization) return\r\n\r\n try {\r\n const patch: Partial<Organization> = {\r\n ...editingValues,\r\n }\r\n\r\n // Upload logo if changed\r\n if (selectedLogoFile) {\r\n const logoKey = await uploadOrganizationLogoToPublicBucket(selectedLogoFile)\r\n patch.logoKey = logoKey\r\n }\r\n\r\n // Upload favicon if changed\r\n if (selectedFaviconFile) {\r\n const faviconKey = await uploadOrganizationLogoToPublicBucket(selectedFaviconFile)\r\n patch.faviconKey = faviconKey\r\n }\r\n\r\n const res = await fetch(`/api/organizations/${organization.id}`, {\r\n method: 'PATCH',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify(patch),\r\n })\r\n\r\n if (!res.ok) throw new Error('Failed to update organization')\r\n\r\n const responseData = await res.json()\r\n const updatedOrg = responseData?.organization ?? responseData\r\n setOrganization(updatedOrg)\r\n mutate(['organization', String(userOrganizationId)], updatedOrg, { revalidate: false })\r\n setEditingValues({})\r\n setIsEditing(false)\r\n setHasChanges(false)\r\n setSelectedLogoFile(null)\r\n setSelectedFaviconFile(null)\r\n toast.success('Organization updated successfully')\r\n } catch (error) {\r\n toast.error('Failed to update organization')\r\n }\r\n }\r\n\r\n const handleCancel = () => {\r\n setEditingValues({})\r\n setIsEditing(false)\r\n setHasChanges(false)\r\n setSelectedLogoFile(null)\r\n setSelectedFaviconFile(null)\r\n }\r\n\r\n //const { state: { runtimeConfig: { minioUrl } } } = useAppConfigContext()\r\n\r\n const logoUrl = selectedLogoFile\r\n ? URL.createObjectURL(selectedLogoFile)\r\n : (getFieldValue('logoKey') as string | null | undefined) && minioBaseUrl\r\n ? `${minioBaseUrl}/org-logos/${getFieldValue('logoKey')}`\r\n : null\r\n\r\n const faviconUrl = selectedFaviconFile\r\n ? URL.createObjectURL(selectedFaviconFile)\r\n : (getFieldValue('faviconKey') as string | null | undefined) && minioBaseUrl\r\n ? `${minioBaseUrl}/org-logos/${getFieldValue('faviconKey')}`\r\n : null\r\n\r\n// const faviconUrl = selectedFaviconFile\r\n// ? URL.createObjectURL(selectedFaviconFile)\r\n// : organization?.faviconKey && minioUrl\r\n// ? `${minioUrl}/org-logos/${organization.faviconKey}`\r\n// : null\r\n \r\n if (isOrganizationLoading) {\r\n return (\r\n <OrganizationSkeleton />\r\n )\r\n }\r\n\r\n if (!organization) {\r\n return (\r\n <div className=\"flex items-center justify-center h-full\">\r\n <p className=\"text-muted-foreground\">{t('messages.noOrganization')}</p>\r\n </div>\r\n )\r\n }\r\n\r\n const countrySubdivisions = \r\n countrySubdivisionsData\r\n ? Object.values(countrySubdivisionsData).map((subdivision: any) => subdivision.name)\r\n : []\r\n\r\n return (\r\n <div className=\"flex flex-col h-full overflow-hidden\">\r\n {/* Header */}\r\n <div className=\"flex justify-between items-center px-6 py-4 border-b\">\r\n <div>\r\n <h2 className=\"text-2xl font-semibold\">{t('header')}</h2>\r\n <p className=\"text-sm text-muted-foreground\">{t('subheader')}</p>\r\n </div>\r\n <div className=\"flex gap-2\">\r\n {isEditing ? (\r\n <>\r\n <Button variant=\"outline\" onClick={handleCancel} disabled={!ability.can('update', \"Organization\")}>\r\n {t('cancel')}\r\n </Button>\r\n <Button onClick={handleSave} disabled={!hasChanges || !ability.can('update', \"Organization\")}>\r\n <LR.Save className=\"mr-2\" />\r\n {t('save')}\r\n </Button>\r\n </>\r\n ) : (\r\n <Button onClick={() => setIsEditing(true)} disabled={!ability.can('update', \"Organization\")}>\r\n <LR.Pencil className=\"mr-2\" />\r\n {t('edit')}\r\n </Button>\r\n )}\r\n </div>\r\n </div>\r\n\r\n {/* Content */}\r\n <div className=\"flex-1 overflow-auto p-6\">\r\n <div className=\"max-w-4xl mx-auto space-y-8\">\r\n {/* Branding Section */}\r\n <div className=\"space-y-4\">\r\n <h3 className=\"text-lg font-semibold\">{t('sections.branding')}</h3>\r\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\r\n {/* Logo */}\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.logo')}</label>\r\n <div className=\"flex items-center gap-4\">\r\n <div className=\"w-32 h-32 border rounded-lg overflow-hidden bg-muted flex items-center justify-center relative group\">\r\n {logoUrl ? (\r\n <img src={logoUrl} alt=\"Organization logo\" className=\"w-full h-full object-contain\" />\r\n ) : (\r\n <LR.Building2 className=\"h-12 w-12 text-muted-foreground\" />\r\n )}\r\n {isEditing && (\r\n <label className=\"absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center cursor-pointer\">\r\n <LR.Upload className=\"h-6 w-6 text-white\" />\r\n <input\r\n type=\"file\"\r\n accept=\"image/*\"\r\n className=\"hidden\"\r\n onChange={(e) => {\r\n const file = e.target.files?.[0]\r\n if (file) {\r\n setSelectedLogoFile(file)\r\n setHasChanges(true)\r\n }\r\n }}\r\n disabled={!ability.can('update', \"Organization\")}\r\n />\r\n </label>\r\n )}\r\n </div>\r\n {isEditing && logoUrl && (\r\n <Button\r\n variant=\"outline\"\r\n size=\"sm\"\r\n onClick={() => {\r\n setSelectedLogoFile(null)\r\n handleInputChange('logoKey', null)\r\n }}\r\n disabled={!ability.can('update', \"Organization\")}\r\n >\r\n Remove\r\n </Button>\r\n )}\r\n </div>\r\n </div>\r\n\r\n {/* Favicon */}\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.favicon')}</label>\r\n <div className=\"flex items-center gap-4\">\r\n <div className=\"w-16 h-16 border rounded-lg overflow-hidden bg-muted flex items-center justify-center relative group\">\r\n {faviconUrl ? (\r\n <img src={faviconUrl} alt=\"Favicon\" className=\"w-full h-full object-contain\" />\r\n ) : (\r\n <LR.Image className=\"h-6 w-6 text-muted-foreground\" />\r\n )}\r\n {isEditing && (\r\n <label className=\"absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center cursor-pointer\">\r\n <LR.Upload className=\"h-4 w-4 text-white\" />\r\n <input\r\n type=\"file\"\r\n accept=\"image/*\"\r\n className=\"hidden\"\r\n onChange={(e) => {\r\n const file = e.target.files?.[0]\r\n if (file) {\r\n setSelectedFaviconFile(file)\r\n setHasChanges(true)\r\n }\r\n }}\r\n disabled={!ability.can('update', \"Organization\")}\r\n />\r\n </label>\r\n )}\r\n </div>\r\n {isEditing && faviconUrl && (\r\n <Button\r\n variant=\"outline\"\r\n size=\"sm\"\r\n onClick={() => {\r\n setSelectedFaviconFile(null)\r\n handleInputChange('faviconKey', null)\r\n }}\r\n disabled={!ability.can('update', \"Organization\")}\r\n >\r\n Remove\r\n </Button>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {/* Colors */}\r\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.mainColor')}</label>\r\n <div className=\"flex gap-2\">\r\n <Input\r\n type=\"color\"\r\n value={(getFieldValue('mainColor') as string) || '#000000'}\r\n onChange={(e) => handleInputChange('mainColor', e.target.value)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n className=\"w-20 h-10 p-1\"\r\n \r\n />\r\n <Input\r\n type=\"text\"\r\n value={(getFieldValue('mainColor') as string) || ''}\r\n onChange={(e) => handleInputChange('mainColor', e.target.value)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n placeholder=\"#000000\"\r\n />\r\n </div>\r\n </div>\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.secondaryColor')}</label>\r\n <div className=\"flex gap-2\">\r\n <Input\r\n type=\"color\"\r\n value={(getFieldValue('secondaryColor') as string) || '#000000'}\r\n onChange={(e) => handleInputChange('secondaryColor', e.target.value)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n className=\"w-20 h-10 p-1\"\r\n />\r\n <Input\r\n type=\"text\"\r\n value={(getFieldValue('secondaryColor') as string) || ''}\r\n onChange={(e) => handleInputChange('secondaryColor', e.target.value)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n placeholder=\"#000000\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <Separator />\r\n\r\n {/* Basic Information */}\r\n <div className=\"space-y-4\">\r\n <h3 className=\"text-lg font-semibold\">{t('sections.basicInfo')}</h3>\r\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.name')}</label>\r\n <Input\r\n value={(organization?.name as string) || ''}\r\n disabled\r\n readOnly\r\n />\r\n <p className=\"text-xs text-muted-foreground\">{t('fields.nameLocked')}</p>\r\n </div>\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.title')}</label>\r\n <Input\r\n value={(getFieldValue('title') as string) || ''}\r\n onChange={(e) => handleInputChange('title', e.target.value)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\") }\r\n />\r\n </div>\r\n </div>\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.description')}</label>\r\n <Textarea\r\n value={(getFieldValue('description') as string) || ''}\r\n onChange={(e) => handleInputChange('description', e.target.value)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n rows={4}\r\n />\r\n </div>\r\n </div>\r\n\r\n <Separator />\r\n\r\n {/* Location */}\r\n <div className=\"space-y-4\">\r\n <h3 className=\"text-lg font-semibold\">{t('sections.location')}</h3>\r\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.countrySubdivision')}</label>\r\n <Select\r\n value={String(getFieldValue('countrySubdivision') || '')}\r\n onValueChange={(value) => handleInputChange('countrySubdivision', value)}\r\n disabled={!isEditing}\r\n >\r\n <SelectTrigger>\r\n <SelectValue placeholder={t('placeholders.selectProvince')} />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {countrySubdivisions.map((csName) => (\r\n <SelectItem key={csName} value={csName}>\r\n {csName}\r\n </SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n </div>\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.municipality')}</label>\r\n <Input\r\n value={(getFieldValue('municipality') as string) || ''}\r\n onChange={(e) => handleInputChange('municipality', e.target.value)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n />\r\n </div>\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.postalCode')}</label>\r\n <Input\r\n value={(getFieldValue('postalCode') as string) || ''}\r\n onChange={(e) => handleInputChange('postalCode', e.target.value)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n />\r\n </div>\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.locationId')}</label>\r\n <Input\r\n value={(getFieldValue('locationId') as string) || ''}\r\n onChange={(e) => handleInputChange('locationId', e.target.value)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <Separator />\r\n\r\n {/* Map Configuration */}\r\n <div className=\"space-y-4\">\r\n <h3 className=\"text-lg font-semibold\">{t('sections.mapConfig')}</h3>\r\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4\">\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.latitude')}</label>\r\n <Input\r\n type=\"number\"\r\n step=\"any\"\r\n value={(getFieldValue('lat') as number) ?? ''}\r\n onChange={(e) => handleInputChange('lat', e.target.value ? parseFloat(e.target.value) : null)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n />\r\n </div>\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.longitude')}</label>\r\n <Input\r\n type=\"number\"\r\n step=\"any\"\r\n value={(getFieldValue('long') as number) ?? ''}\r\n onChange={(e) => handleInputChange('long', e.target.value ? parseFloat(e.target.value) : null)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n />\r\n </div>\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.zoom')}</label>\r\n <Input\r\n type=\"number\"\r\n step=\"any\"\r\n value={(getFieldValue('zoom') as number) ?? ''}\r\n onChange={(e) => handleInputChange('zoom', e.target.value ? parseFloat(e.target.value) : null)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n />\r\n </div>\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.minZoom')}</label>\r\n <Input\r\n type=\"number\"\r\n step=\"any\"\r\n value={(getFieldValue('minZoom') as number) ?? ''}\r\n onChange={(e) => handleInputChange('minZoom', e.target.value ? parseFloat(e.target.value) : null)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n />\r\n </div>\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.bearing')}</label>\r\n <Input\r\n type=\"number\"\r\n step=\"any\"\r\n value={(getFieldValue('bearing') as number) ?? ''}\r\n onChange={(e) => handleInputChange('bearing', e.target.value ? parseFloat(e.target.value) : null)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n />\r\n </div>\r\n <div className=\"space-y-2\">\r\n <label className=\"text-sm font-medium\">{t('fields.pitch')}</label>\r\n <Input\r\n type=\"number\"\r\n step=\"any\"\r\n value={(getFieldValue('pitch') as number) ?? ''}\r\n onChange={(e) => handleInputChange('pitch', e.target.value ? parseFloat(e.target.value) : null)}\r\n disabled={!isEditing || !ability.can('update', \"Organization\")}\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n )\r\n}\r\n"],"mappings":"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","names":["_a"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SettingsSidebar.d.ts","sourceRoot":"","sources":["../../../../../src/core/components/settings/src/SettingsSidebar.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SettingsSidebar.d.ts","sourceRoot":"","sources":["../../../../../src/core/components/settings/src/SettingsSidebar.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAI7C,KAAK,WAAW,GAAG;IACjB,GAAG,EAAE,cAAc,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,KAAK,oBAAoB,GAAG;IAC1B,IAAI,EAAE,WAAW,EAAE,CAAA;IACnB,SAAS,EAAE,cAAc,CAAA;IACzB,WAAW,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAA;CAC3C,CAAA;AAED,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,EAAE,oBAAoB,+BA8D7F"}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Button } from "../../ui/";
|
|
4
4
|
import {
|
|
5
5
|
SidebarMenu,
|
|
6
6
|
SidebarMenuButton,
|
|
7
7
|
SidebarMenuItem
|
|
8
8
|
} from "../../ui/Sidebar";
|
|
9
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../ui/Select";
|
|
9
10
|
import { usePermissions } from "../../../store";
|
|
11
|
+
import { useIsMobile } from "../../../hooks/ui/use-mobile";
|
|
10
12
|
function SettingsSidebar({ tabs, activeTab, onTabChange }) {
|
|
11
13
|
const { ability } = usePermissions();
|
|
14
|
+
const isMobile = useIsMobile();
|
|
12
15
|
const isTabVisible = (tabKey) => {
|
|
13
16
|
if (tabKey === "users") {
|
|
14
17
|
return ability.can("update", "Role");
|
|
@@ -18,7 +21,14 @@ function SettingsSidebar({ tabs, activeTab, onTabChange }) {
|
|
|
18
21
|
}
|
|
19
22
|
return true;
|
|
20
23
|
};
|
|
21
|
-
|
|
24
|
+
const visibleTabs = tabs.filter((tab) => isTabVisible(tab.key));
|
|
25
|
+
if (isMobile) {
|
|
26
|
+
return /* @__PURE__ */ jsx("div", { className: "w-full", children: /* @__PURE__ */ jsxs(Select, { value: activeTab, onValueChange: (value) => onTabChange(value), children: [
|
|
27
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
28
|
+
/* @__PURE__ */ jsx(SelectContent, { children: visibleTabs.map((tab) => /* @__PURE__ */ jsx(SelectItem, { value: tab.key, disabled: tab.disabled, children: tab.label }, tab.key)) })
|
|
29
|
+
] }) });
|
|
30
|
+
}
|
|
31
|
+
return /* @__PURE__ */ jsx(SidebarMenu, { className: "w-full", children: visibleTabs.map((tab) => /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsx(SidebarMenuButton, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
22
32
|
Button,
|
|
23
33
|
{
|
|
24
34
|
className: `text-sm flex justify-start pointer-events-auto cursor-pointer ${activeTab === tab.key ? "bg-accent" : ""}`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/core/components/settings/src/SettingsSidebar.tsx"],"sourcesContent":["\"use client\"\r\n\r\n// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport { Button } from '../../ui/'\r\nimport {\r\n SidebarMenu,\r\n SidebarMenuButton,\r\n SidebarMenuItem,\r\n} from '../../ui/Sidebar'\r\nimport type { SettingsTabKey } from './types'\r\nimport { usePermissions } from '../../../store'\r\n\r\ntype SettingsTab = {\r\n key: SettingsTabKey\r\n label: string\r\n disabled?: boolean\r\n}\r\n\r\ntype SettingsSidebarProps = {\r\n tabs: SettingsTab[]\r\n activeTab: SettingsTabKey\r\n onTabChange: (tab: SettingsTabKey) => void\r\n}\r\n\r\nexport default function SettingsSidebar({ tabs, activeTab, onTabChange }: SettingsSidebarProps) {\r\n
|
|
1
|
+
{"version":3,"sources":["../../../../../src/core/components/settings/src/SettingsSidebar.tsx"],"sourcesContent":["\"use client\"\r\n\r\n// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport { Button } from '../../ui/'\r\nimport {\r\n SidebarMenu,\r\n SidebarMenuButton,\r\n SidebarMenuItem,\r\n} from '../../ui/Sidebar'\r\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../ui/Select'\r\nimport type { SettingsTabKey } from './types'\r\nimport { usePermissions } from '../../../store'\r\nimport { useIsMobile } from '../../../hooks/ui/use-mobile'\r\n\r\ntype SettingsTab = {\r\n key: SettingsTabKey\r\n label: string\r\n disabled?: boolean\r\n}\r\n\r\ntype SettingsSidebarProps = {\r\n tabs: SettingsTab[]\r\n activeTab: SettingsTabKey\r\n onTabChange: (tab: SettingsTabKey) => void\r\n}\r\n\r\nexport default function SettingsSidebar({ tabs, activeTab, onTabChange }: SettingsSidebarProps) {\r\n\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n const isMobile = useIsMobile()\r\n\r\n // Helper to check if tab should be visible\r\n const isTabVisible = (tabKey: SettingsTabKey) => {\r\n if (tabKey === 'users') {\r\n return ability.can('update', 'Role')\r\n }\r\n if (tabKey === 'organization') {\r\n return ability.can('update', 'Organization')\r\n }\r\n return true\r\n }\r\n\r\n const visibleTabs = tabs.filter(tab => isTabVisible(tab.key))\r\n\r\n // Mobile: the desktop sidebar list below is `hidden` under md, with no\r\n // other way to reach it — swap to a dropdown so tabs (e.g. \"Organization\")\r\n // stay reachable on narrow viewports. Branching on isMobile (rather than\r\n // hiding via CSS classes on the desktop markup) keeps the desktop render\r\n // path byte-for-byte unchanged from before.\r\n if (isMobile) {\r\n return (\r\n <div className=\"w-full\">\r\n <Select value={activeTab} onValueChange={value => onTabChange(value as SettingsTabKey)}>\r\n <SelectTrigger>\r\n <SelectValue />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {visibleTabs.map(tab => (\r\n <SelectItem key={tab.key} value={tab.key} disabled={tab.disabled}>\r\n {tab.label}\r\n </SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n </div>\r\n )\r\n }\r\n\r\n return (\r\n <SidebarMenu className=\"w-full\">\r\n {visibleTabs.map(tab => (\r\n <SidebarMenuItem key={tab.key}>\r\n <SidebarMenuButton asChild>\r\n <Button\r\n className={`text-sm flex justify-start pointer-events-auto cursor-pointer ${activeTab === tab.key ? 'bg-accent' : ''}`}\r\n variant=\"ghost\"\r\n onClick={() => onTabChange(tab.key)}\r\n disabled={tab.disabled}\r\n >\r\n <span>{tab.label}</span>\r\n </Button>\r\n </SidebarMenuButton>\r\n </SidebarMenuItem>\r\n ))}\r\n </SidebarMenu>\r\n )\r\n}\r\n"],"mappings":";AAwDQ,SAEI,KAFJ;AAnDR,SAAS,cAAc;AACvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,QAAQ,eAAe,YAAY,eAAe,mBAAmB;AAE9E,SAAS,sBAAsB;AAC/B,SAAS,mBAAmB;AAcb,SAAR,gBAAiC,EAAE,MAAM,WAAW,YAAY,GAAyB;AAG9F,QAAM,EAAE,QAAQ,IAAI,eAAe;AAEnC,QAAM,WAAW,YAAY;AAG7B,QAAM,eAAe,CAAC,WAA2B;AAC/C,QAAI,WAAW,SAAS;AACtB,aAAO,QAAQ,IAAI,UAAU,MAAM;AAAA,IACrC;AACA,QAAI,WAAW,gBAAgB;AAC7B,aAAO,QAAQ,IAAI,UAAU,cAAc;AAAA,IAC7C;AACA,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,KAAK,OAAO,SAAO,aAAa,IAAI,GAAG,CAAC;AAO5D,MAAI,UAAU;AACZ,WACE,oBAAC,SAAI,WAAU,UACb,+BAAC,UAAO,OAAO,WAAW,eAAe,WAAS,YAAY,KAAuB,GACnF;AAAA,0BAAC,iBACC,8BAAC,eAAY,GACf;AAAA,MACA,oBAAC,iBACE,sBAAY,IAAI,SACf,oBAAC,cAAyB,OAAO,IAAI,KAAK,UAAU,IAAI,UACrD,cAAI,SADU,IAAI,GAErB,CACD,GACH;AAAA,OACF,GACF;AAAA,EAEJ;AAEA,SACE,oBAAC,eAAY,WAAU,UACpB,sBAAY,IAAI,SACf,oBAAC,mBACC,8BAAC,qBAAkB,SAAO,MACxB;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,iEAAiE,cAAc,IAAI,MAAM,cAAc,EAAE;AAAA,MACpH,SAAQ;AAAA,MACR,SAAS,MAAM,YAAY,IAAI,GAAG;AAAA,MAClC,UAAU,IAAI;AAAA,MAEd,8BAAC,UAAM,cAAI,OAAM;AAAA;AAAA,EACnB,GACF,KAVoB,IAAI,GAW1B,CACD,GACH;AAEJ;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BugReportDialog.d.ts","sourceRoot":"","sources":["../../../../src/core/components/support/BugReportDialog.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"BugReportDialog.d.ts","sourceRoot":"","sources":["../../../../src/core/components/support/BugReportDialog.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAsF/B,wBAAgB,eAAe,CAAC,EAC9B,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,MAAM,GACP,EAAE;IACD,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,qBAuNA"}
|
|
@@ -12,6 +12,8 @@ import { Button } from "../ui/Button";
|
|
|
12
12
|
import { Textarea } from "../ui/Textarea";
|
|
13
13
|
import { Input } from "../ui/Input";
|
|
14
14
|
import { useCaptureScreenshot } from "../../hooks/useCaptureScreenshot";
|
|
15
|
+
import { useCurrentViewerPosition } from "../../hooks/useCurrentViewerPosition";
|
|
16
|
+
import { useShareUrl } from "../../hooks/useShareUrl";
|
|
15
17
|
import * as LR from "lucide-react";
|
|
16
18
|
import { toast } from "sonner";
|
|
17
19
|
function getBrowserInfo() {
|
|
@@ -38,11 +40,32 @@ function getBrowserInfo() {
|
|
|
38
40
|
return "Unknown";
|
|
39
41
|
}
|
|
40
42
|
function getDeviceInfo() {
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
return
|
|
43
|
+
const ua = navigator.userAgent;
|
|
44
|
+
if (/iPhone/i.test(ua)) {
|
|
45
|
+
return "Mobile - iPhone";
|
|
46
|
+
}
|
|
47
|
+
if (/Android/i.test(ua) && /Mobile/i.test(ua)) {
|
|
48
|
+
return "Mobile - Android";
|
|
49
|
+
}
|
|
50
|
+
if (/iPad/i.test(ua)) {
|
|
51
|
+
return "Tablet - iPad";
|
|
52
|
+
}
|
|
53
|
+
if (/Android/i.test(ua) && !/Mobile/i.test(ua)) {
|
|
54
|
+
return "Tablet - Android";
|
|
55
|
+
}
|
|
56
|
+
if (/Windows NT/i.test(ua)) {
|
|
57
|
+
return "Desktop - Windows";
|
|
58
|
+
}
|
|
59
|
+
if (/Macintosh|Mac OS X/i.test(ua)) {
|
|
60
|
+
return "Desktop - macOS";
|
|
44
61
|
}
|
|
45
|
-
|
|
62
|
+
if (/CrOS/i.test(ua)) {
|
|
63
|
+
return "Desktop - ChromeOS";
|
|
64
|
+
}
|
|
65
|
+
if (/Linux/i.test(ua)) {
|
|
66
|
+
return "Desktop - Linux";
|
|
67
|
+
}
|
|
68
|
+
return "Unknown";
|
|
46
69
|
}
|
|
47
70
|
function BugReportDialog({
|
|
48
71
|
open,
|
|
@@ -52,28 +75,67 @@ function BugReportDialog({
|
|
|
52
75
|
}) {
|
|
53
76
|
const t = useTranslations("supportDialog");
|
|
54
77
|
const captureScreenshot = useCaptureScreenshot();
|
|
78
|
+
const getPosition = useCurrentViewerPosition();
|
|
79
|
+
const getShareUrl = useShareUrl();
|
|
55
80
|
const [title, setTitle] = React.useState("");
|
|
56
81
|
const [description, setDescription] = React.useState("");
|
|
57
82
|
const [repro, setRepro] = React.useState("");
|
|
58
83
|
const [screenshot, setScreenshot] = React.useState(null);
|
|
84
|
+
const [previewScreenshot, setPreviewScreenshot] = React.useState(null);
|
|
59
85
|
const [loading, setLoading] = React.useState(false);
|
|
86
|
+
const [capturing, setCapturing] = React.useState(false);
|
|
60
87
|
const [error, setError] = React.useState(null);
|
|
88
|
+
const previewRef = React.useRef(null);
|
|
89
|
+
const errorRef = React.useRef(null);
|
|
90
|
+
React.useEffect(() => {
|
|
91
|
+
var _a;
|
|
92
|
+
if (previewScreenshot) {
|
|
93
|
+
(_a = previewRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
94
|
+
}
|
|
95
|
+
}, [previewScreenshot]);
|
|
96
|
+
React.useEffect(() => {
|
|
97
|
+
var _a;
|
|
98
|
+
if (error) {
|
|
99
|
+
(_a = errorRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
100
|
+
}
|
|
101
|
+
}, [error]);
|
|
61
102
|
const reset = () => {
|
|
62
103
|
setTitle("");
|
|
63
104
|
setDescription("");
|
|
64
105
|
setRepro("");
|
|
65
106
|
setScreenshot(null);
|
|
107
|
+
setPreviewScreenshot(null);
|
|
66
108
|
setError(null);
|
|
67
109
|
};
|
|
68
110
|
const capture = async () => {
|
|
69
|
-
|
|
70
|
-
|
|
111
|
+
setCapturing(true);
|
|
112
|
+
setError(null);
|
|
113
|
+
try {
|
|
114
|
+
const img = await captureScreenshot();
|
|
115
|
+
if (img) {
|
|
116
|
+
setPreviewScreenshot(img);
|
|
117
|
+
} else {
|
|
118
|
+
setError("Screenshot failed \u2014 the image came back empty.");
|
|
119
|
+
}
|
|
120
|
+
} catch (err) {
|
|
121
|
+
setError(`Screenshot failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
122
|
+
} finally {
|
|
123
|
+
setCapturing(false);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const confirmScreenshot = () => {
|
|
127
|
+
setScreenshot(previewScreenshot);
|
|
128
|
+
setPreviewScreenshot(null);
|
|
129
|
+
};
|
|
130
|
+
const retakeScreenshot = () => {
|
|
131
|
+
setPreviewScreenshot(null);
|
|
71
132
|
};
|
|
72
133
|
const submit = async () => {
|
|
73
134
|
if (!title.trim()) return;
|
|
74
135
|
try {
|
|
75
136
|
setLoading(true);
|
|
76
137
|
setError(null);
|
|
138
|
+
const shareUrl = await getShareUrl();
|
|
77
139
|
const res = await fetch("/api/github-issue", {
|
|
78
140
|
method: "POST",
|
|
79
141
|
headers: { "Content-Type": "application/json" },
|
|
@@ -85,13 +147,14 @@ function BugReportDialog({
|
|
|
85
147
|
type: "bug",
|
|
86
148
|
labels: ["users"],
|
|
87
149
|
meta: {
|
|
88
|
-
url:
|
|
150
|
+
url: shareUrl,
|
|
89
151
|
userAgent: navigator.userAgent,
|
|
90
152
|
browser: getBrowserInfo(),
|
|
91
153
|
device: getDeviceInfo(),
|
|
92
154
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
93
155
|
viewer,
|
|
94
|
-
userEmail
|
|
156
|
+
userEmail,
|
|
157
|
+
position: getPosition()
|
|
95
158
|
}
|
|
96
159
|
})
|
|
97
160
|
});
|
|
@@ -147,16 +210,34 @@ function BugReportDialog({
|
|
|
147
210
|
}
|
|
148
211
|
),
|
|
149
212
|
/* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center", children: [
|
|
150
|
-
/* @__PURE__ */ jsxs(Button, { onClick: capture, variant: "secondary", disabled: loading, children: [
|
|
213
|
+
/* @__PURE__ */ jsxs(Button, { onClick: capture, variant: "secondary", disabled: loading || capturing, children: [
|
|
151
214
|
/* @__PURE__ */ jsx(LR.Camera, { className: "h-4 w-4 mr-1" }),
|
|
152
|
-
t("captureScreenshot")
|
|
215
|
+
capturing ? t("capturing") : t("captureScreenshot")
|
|
153
216
|
] }),
|
|
154
|
-
screenshot && /* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground flex items-center gap-1", children: [
|
|
217
|
+
screenshot && !previewScreenshot && /* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground flex items-center gap-1", children: [
|
|
155
218
|
/* @__PURE__ */ jsx(LR.CheckCircle, { className: "h-3 w-3 text-green-500" }),
|
|
156
219
|
t("screenshotAttached")
|
|
157
220
|
] })
|
|
158
221
|
] }),
|
|
159
|
-
|
|
222
|
+
previewScreenshot && /* @__PURE__ */ jsxs("div", { ref: previewRef, className: "flex flex-col gap-2 rounded border p-2", children: [
|
|
223
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t("screenshotPreviewTitle") }),
|
|
224
|
+
/* @__PURE__ */ jsx(
|
|
225
|
+
"img",
|
|
226
|
+
{
|
|
227
|
+
src: previewScreenshot,
|
|
228
|
+
alt: "screenshot preview",
|
|
229
|
+
className: "w-full h-auto rounded border object-contain max-h-[60vh]"
|
|
230
|
+
}
|
|
231
|
+
),
|
|
232
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
233
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: retakeScreenshot, children: t("retake") }),
|
|
234
|
+
/* @__PURE__ */ jsxs(Button, { onClick: confirmScreenshot, children: [
|
|
235
|
+
/* @__PURE__ */ jsx(LR.Check, { className: "h-4 w-4 mr-1" }),
|
|
236
|
+
t("attachScreenshot")
|
|
237
|
+
] })
|
|
238
|
+
] })
|
|
239
|
+
] }),
|
|
240
|
+
screenshot && !previewScreenshot && /* @__PURE__ */ jsx(
|
|
160
241
|
"img",
|
|
161
242
|
{
|
|
162
243
|
src: screenshot,
|
|
@@ -164,7 +245,7 @@ function BugReportDialog({
|
|
|
164
245
|
className: "w-full h-auto rounded border max-h-40 object-contain"
|
|
165
246
|
}
|
|
166
247
|
),
|
|
167
|
-
error && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error }),
|
|
248
|
+
error && /* @__PURE__ */ jsx("p", { ref: errorRef, className: "text-sm text-destructive", children: error }),
|
|
168
249
|
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
169
250
|
/* @__PURE__ */ jsx(
|
|
170
251
|
Button,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/core/components/support/BugReportDialog.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport * as React from \"react\";\r\nimport { useTranslations } from \"next-intl\";\r\n\r\nimport {\r\n Dialog,\r\n DialogContent,\r\n DialogHeader,\r\n DialogTitle,\r\n} from \"../ui/Dialog\";\r\n\r\nimport { Button } from \"../ui/Button\";\r\nimport { Textarea } from \"../ui/Textarea\";\r\nimport { Input } from \"../ui/Input\";\r\nimport { useCaptureScreenshot } from \"../../hooks/useCaptureScreenshot\";\r\n\r\nimport * as LR from \"lucide-react\";\r\nimport { toast } from \"sonner\";\r\n\r\nfunction getBrowserInfo(): string {\r\n const ua = navigator.userAgent;\r\n if (\"userAgentData\" in navigator) {\r\n const uaData = (navigator as any).userAgentData;\r\n const brands: Array<{ brand: string; version: string }> = uaData.brands ?? [];\r\n const brand =\r\n brands.find((b) => !b.brand.includes(\"Not\") && b.brand !== \"Chromium\") ??\r\n brands[0];\r\n const name = brand?.brand ?? \"Unknown\";\r\n const version = brand?.version ?? \"\";\r\n return version ? `${name} ${version}` : name;\r\n }\r\n const checks: Array<[string, RegExp]> = [\r\n [\"Firefox\", /Firefox\\/(\\d+)/],\r\n [\"Edge\", /Edg\\/(\\d+)/],\r\n [\"Chrome\", /Chrome\\/(\\d+)/],\r\n [\"Safari\", /Version\\/(\\d+).*Safari/],\r\n ];\r\n for (const [name, re] of checks) {\r\n const m = re.exec(ua);\r\n if (m) return `${name} ${m[1]}`;\r\n }\r\n return \"Unknown\";\r\n}\r\n\r\nfunction getDeviceInfo(): string {\r\n if (\"userAgentData\" in navigator) {\r\n return (navigator as any).userAgentData?.mobile ? \"Mobile\" : \"Desktop\";\r\n }\r\n return /Mobile|Android|iPhone|iPad|iPod/.test(navigator.userAgent)\r\n ? \"Mobile\"\r\n : \"Desktop\";\r\n}\r\n\r\nexport function BugReportDialog({\r\n open,\r\n onOpenChange,\r\n userEmail,\r\n viewer,\r\n}: {\r\n open: boolean;\r\n onOpenChange: (v: boolean) => void;\r\n userEmail?: string;\r\n viewer?: string;\r\n}) {\r\n const t = useTranslations(\"supportDialog\");\r\n const captureScreenshot = useCaptureScreenshot();\r\n\r\n const [title, setTitle] = React.useState(\"\");\r\n const [description, setDescription] = React.useState(\"\");\r\n const [repro, setRepro] = React.useState(\"\");\r\n const [screenshot, setScreenshot] = React.useState<string | null>(null);\r\n const [loading, setLoading] = React.useState(false);\r\n const [error, setError] = React.useState<string | null>(null);\r\n\r\n const reset = () => {\r\n setTitle(\"\");\r\n setDescription(\"\");\r\n setRepro(\"\");\r\n setScreenshot(null);\r\n setError(null);\r\n };\r\n\r\n const capture = async () => {\r\n const img = await captureScreenshot();\r\n setScreenshot(img);\r\n };\r\n\r\n const submit = async () => {\r\n if (!title.trim()) return;\r\n try {\r\n setLoading(true);\r\n setError(null);\r\n\r\n const res = await fetch(\"/api/github-issue\", {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: JSON.stringify({\r\n title,\r\n description,\r\n repro,\r\n screenshot,\r\n type: \"bug\",\r\n labels: [\"users\"],\r\n meta: {\r\n url: window.location.href,\r\n userAgent: navigator.userAgent,\r\n browser: getBrowserInfo(),\r\n device: getDeviceInfo(),\r\n timestamp: new Date().toISOString(),\r\n viewer,\r\n userEmail,\r\n },\r\n }),\r\n });\r\n\r\n if (!res.ok) {\r\n const data = await res.json().catch(() => ({}));\r\n throw new Error(data.error || \"Submission failed\");\r\n }\r\n\r\n reset();\r\n onOpenChange(false);\r\n toast.success(t(\"submitSuccess\"));\r\n } catch (err) {\r\n setError(err instanceof Error ? err.message : \"Something went wrong\");\r\n } finally {\r\n setLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <Dialog\r\n open={open}\r\n onOpenChange={(v) => {\r\n onOpenChange(v);\r\n if (!v) reset();\r\n }}\r\n >\r\n <DialogContent className=\"sm:max-w-[520px]\">\r\n <DialogHeader>\r\n <DialogTitle className=\"flex items-center gap-2\">\r\n <LR.Bug className=\"h-5 w-5\" />\r\n {t(\"bugTitle\")}\r\n </DialogTitle>\r\n </DialogHeader>\r\n\r\n <div className=\"space-y-3\">\r\n <Input\r\n placeholder={t(\"bugTitlePlaceholder\")}\r\n value={title}\r\n onChange={(e) => setTitle(e.target.value)}\r\n />\r\n\r\n <Textarea\r\n placeholder={t(\"descriptionPlaceholder\")}\r\n value={description}\r\n onChange={(e) => setDescription(e.target.value)}\r\n />\r\n\r\n <Textarea\r\n placeholder={t(\"reproPlaceholder\")}\r\n value={repro}\r\n onChange={(e) => setRepro(e.target.value)}\r\n />\r\n\r\n <div className=\"flex gap-2 items-center\">\r\n <Button onClick={capture} variant=\"secondary\" disabled={loading}>\r\n <LR.Camera className=\"h-4 w-4 mr-1\" />\r\n {t(\"captureScreenshot\")}\r\n </Button>\r\n {screenshot && (\r\n <span className=\"text-sm text-muted-foreground flex items-center gap-1\">\r\n <LR.CheckCircle className=\"h-3 w-3 text-green-500\" />\r\n {t(\"screenshotAttached\")}\r\n </span>\r\n )}\r\n </div>\r\n\r\n {screenshot && (\r\n <img\r\n src={screenshot}\r\n alt=\"screenshot preview\"\r\n className=\"w-full h-auto rounded border max-h-40 object-contain\"\r\n />\r\n )}\r\n\r\n {error && (\r\n <p className=\"text-sm text-destructive\">{error}</p>\r\n )}\r\n\r\n <div className=\"flex justify-end gap-2\">\r\n <Button\r\n variant=\"secondary\"\r\n onClick={() => onOpenChange(false)}\r\n disabled={loading}\r\n >\r\n {t(\"cancel\")}\r\n </Button>\r\n\r\n <Button\r\n onClick={submit}\r\n disabled={!title.trim() || loading}\r\n >\r\n {loading ? \"...\" : t(\"submit\")}\r\n </Button>\r\n </div>\r\n </div>\r\n </DialogContent>\r\n </Dialog>\r\n );\r\n}\r\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/core/components/support/BugReportDialog.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport * as React from \"react\";\r\nimport { useTranslations } from \"next-intl\";\r\n\r\nimport {\r\n Dialog,\r\n DialogContent,\r\n DialogHeader,\r\n DialogTitle,\r\n} from \"../ui/Dialog\";\r\n\r\nimport { Button } from \"../ui/Button\";\r\nimport { Textarea } from \"../ui/Textarea\";\r\nimport { Input } from \"../ui/Input\";\r\nimport { useCaptureScreenshot } from \"../../hooks/useCaptureScreenshot\";\r\nimport { useCurrentViewerPosition } from \"../../hooks/useCurrentViewerPosition\";\r\nimport { useShareUrl } from \"../../hooks/useShareUrl\";\r\n\r\nimport * as LR from \"lucide-react\";\r\nimport { toast } from \"sonner\";\r\n\r\nfunction getBrowserInfo(): string {\r\n const ua = navigator.userAgent;\r\n if (\"userAgentData\" in navigator) {\r\n const uaData = (navigator as any).userAgentData;\r\n const brands: Array<{ brand: string; version: string }> = uaData.brands ?? [];\r\n const brand =\r\n brands.find((b) => !b.brand.includes(\"Not\") && b.brand !== \"Chromium\") ??\r\n brands[0];\r\n const name = brand?.brand ?? \"Unknown\";\r\n const version = brand?.version ?? \"\";\r\n return version ? `${name} ${version}` : name;\r\n }\r\n const checks: Array<[string, RegExp]> = [\r\n [\"Firefox\", /Firefox\\/(\\d+)/],\r\n [\"Edge\", /Edg\\/(\\d+)/],\r\n [\"Chrome\", /Chrome\\/(\\d+)/],\r\n [\"Safari\", /Version\\/(\\d+).*Safari/],\r\n ];\r\n for (const [name, re] of checks) {\r\n const m = re.exec(ua);\r\n if (m) return `${name} ${m[1]}`;\r\n }\r\n return \"Unknown\";\r\n}\r\n\r\nfunction getDeviceInfo(): string {\r\n const ua = navigator.userAgent;\r\n\r\n // Mobile\r\n if (/iPhone/i.test(ua)) {\r\n return \"Mobile - iPhone\";\r\n }\r\n\r\n if (/Android/i.test(ua) && /Mobile/i.test(ua)) {\r\n return \"Mobile - Android\";\r\n }\r\n\r\n // Tablets\r\n if (/iPad/i.test(ua)) {\r\n return \"Tablet - iPad\";\r\n }\r\n\r\n if (/Android/i.test(ua) && !/Mobile/i.test(ua)) {\r\n return \"Tablet - Android\";\r\n }\r\n\r\n // Desktop OS\r\n if (/Windows NT/i.test(ua)) {\r\n return \"Desktop - Windows\";\r\n }\r\n\r\n if (/Macintosh|Mac OS X/i.test(ua)) {\r\n return \"Desktop - macOS\";\r\n }\r\n\r\n if (/CrOS/i.test(ua)) {\r\n return \"Desktop - ChromeOS\";\r\n }\r\n\r\n if (/Linux/i.test(ua)) {\r\n return \"Desktop - Linux\";\r\n }\r\n\r\n return \"Unknown\";\r\n}\r\n\r\nexport function BugReportDialog({\r\n open,\r\n onOpenChange,\r\n userEmail,\r\n viewer,\r\n}: {\r\n open: boolean;\r\n onOpenChange: (v: boolean) => void;\r\n userEmail?: string;\r\n viewer?: string;\r\n}) {\r\n const t = useTranslations(\"supportDialog\");\r\n const captureScreenshot = useCaptureScreenshot();\r\n const getPosition = useCurrentViewerPosition();\r\n const getShareUrl = useShareUrl();\r\n\r\n const [title, setTitle] = React.useState(\"\");\r\n const [description, setDescription] = React.useState(\"\");\r\n const [repro, setRepro] = React.useState(\"\");\r\n const [screenshot, setScreenshot] = React.useState<string | null>(null);\r\n // Holds a just-captured image pending the user's confirmation — not\r\n // attached to the report until they explicitly accept it via `OK`.\r\n const [previewScreenshot, setPreviewScreenshot] = React.useState<string | null>(null);\r\n const [loading, setLoading] = React.useState(false);\r\n const [capturing, setCapturing] = React.useState(false);\r\n const [error, setError] = React.useState<string | null>(null);\r\n const previewRef = React.useRef<HTMLDivElement>(null);\r\n const errorRef = React.useRef<HTMLParagraphElement>(null);\r\n\r\n // The dialog body scrolls (max-h-[90vh]); on a short mobile viewport the\r\n // preview OR the error message can render below whatever's currently in\r\n // view with nothing to bring it on-screen automatically — a big desktop\r\n // viewport just happens to fit everything so this never showed up there.\r\n React.useEffect(() => {\r\n if (previewScreenshot) {\r\n previewRef.current?.scrollIntoView({ behavior: \"smooth\", block: \"nearest\" });\r\n }\r\n }, [previewScreenshot]);\r\n\r\n React.useEffect(() => {\r\n if (error) {\r\n errorRef.current?.scrollIntoView({ behavior: \"smooth\", block: \"nearest\" });\r\n }\r\n }, [error]);\r\n\r\n const reset = () => {\r\n setTitle(\"\");\r\n setDescription(\"\");\r\n setRepro(\"\");\r\n setScreenshot(null);\r\n setPreviewScreenshot(null);\r\n setError(null);\r\n };\r\n\r\n const capture = async () => {\r\n setCapturing(true);\r\n setError(null);\r\n try {\r\n const img = await captureScreenshot();\r\n if (img) {\r\n setPreviewScreenshot(img);\r\n } else {\r\n setError('Screenshot failed — the image came back empty.');\r\n }\r\n } catch (err) {\r\n setError(`Screenshot failed: ${err instanceof Error ? err.message : String(err)}`);\r\n } finally {\r\n setCapturing(false);\r\n }\r\n };\r\n\r\n const confirmScreenshot = () => {\r\n setScreenshot(previewScreenshot);\r\n setPreviewScreenshot(null);\r\n };\r\n\r\n const retakeScreenshot = () => {\r\n setPreviewScreenshot(null);\r\n };\r\n\r\n const submit = async () => {\r\n if (!title.trim()) return;\r\n try {\r\n setLoading(true);\r\n setError(null);\r\n\r\n const shareUrl = await getShareUrl();\r\n const res = await fetch(\"/api/github-issue\", {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: JSON.stringify({\r\n title,\r\n description,\r\n repro,\r\n screenshot,\r\n type: \"bug\",\r\n labels: [\"users\"],\r\n meta: {\r\n url: shareUrl,\r\n userAgent: navigator.userAgent,\r\n browser: getBrowserInfo(),\r\n device: getDeviceInfo(),\r\n timestamp: new Date().toISOString(),\r\n viewer,\r\n userEmail,\r\n position: getPosition(),\r\n },\r\n }),\r\n });\r\n\r\n if (!res.ok) {\r\n const data = await res.json().catch(() => ({}));\r\n throw new Error(data.error || \"Submission failed\");\r\n }\r\n\r\n reset();\r\n onOpenChange(false);\r\n toast.success(t(\"submitSuccess\"));\r\n } catch (err) {\r\n setError(err instanceof Error ? err.message : \"Something went wrong\");\r\n } finally {\r\n setLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <Dialog\r\n open={open}\r\n onOpenChange={(v) => {\r\n onOpenChange(v);\r\n if (!v) reset();\r\n }}\r\n >\r\n <DialogContent className=\"sm:max-w-[520px]\">\r\n <DialogHeader>\r\n <DialogTitle className=\"flex items-center gap-2\">\r\n <LR.Bug className=\"h-5 w-5\" />\r\n {t(\"bugTitle\")}\r\n </DialogTitle>\r\n </DialogHeader>\r\n\r\n <div className=\"space-y-3\">\r\n <Input\r\n placeholder={t(\"bugTitlePlaceholder\")}\r\n value={title}\r\n onChange={(e) => setTitle(e.target.value)}\r\n />\r\n\r\n <Textarea\r\n placeholder={t(\"descriptionPlaceholder\")}\r\n value={description}\r\n onChange={(e) => setDescription(e.target.value)}\r\n />\r\n\r\n <Textarea\r\n placeholder={t(\"reproPlaceholder\")}\r\n value={repro}\r\n onChange={(e) => setRepro(e.target.value)}\r\n />\r\n\r\n <div className=\"flex gap-2 items-center\">\r\n <Button onClick={capture} variant=\"secondary\" disabled={loading || capturing}>\r\n <LR.Camera className=\"h-4 w-4 mr-1\" />\r\n {capturing ? t(\"capturing\") : t(\"captureScreenshot\")}\r\n </Button>\r\n {screenshot && !previewScreenshot && (\r\n <span className=\"text-sm text-muted-foreground flex items-center gap-1\">\r\n <LR.CheckCircle className=\"h-3 w-3 text-green-500\" />\r\n {t(\"screenshotAttached\")}\r\n </span>\r\n )}\r\n </div>\r\n\r\n {previewScreenshot && (\r\n <div ref={previewRef} className=\"flex flex-col gap-2 rounded border p-2\">\r\n <p className=\"text-sm font-medium\">{t(\"screenshotPreviewTitle\")}</p>\r\n <img\r\n src={previewScreenshot}\r\n alt=\"screenshot preview\"\r\n className=\"w-full h-auto rounded border object-contain max-h-[60vh]\"\r\n />\r\n <div className=\"flex justify-end gap-2\">\r\n <Button variant=\"secondary\" onClick={retakeScreenshot}>\r\n {t(\"retake\")}\r\n </Button>\r\n <Button onClick={confirmScreenshot}>\r\n <LR.Check className=\"h-4 w-4 mr-1\" />\r\n {t(\"attachScreenshot\")}\r\n </Button>\r\n </div>\r\n </div>\r\n )}\r\n\r\n {screenshot && !previewScreenshot && (\r\n <img\r\n src={screenshot}\r\n alt=\"screenshot preview\"\r\n className=\"w-full h-auto rounded border max-h-40 object-contain\"\r\n />\r\n )}\r\n\r\n {error && (\r\n <p ref={errorRef} className=\"text-sm text-destructive\">{error}</p>\r\n )}\r\n\r\n <div className=\"flex justify-end gap-2\">\r\n <Button\r\n variant=\"secondary\"\r\n onClick={() => onOpenChange(false)}\r\n disabled={loading}\r\n >\r\n {t(\"cancel\")}\r\n </Button>\r\n\r\n <Button\r\n onClick={submit}\r\n disabled={!title.trim() || loading}\r\n >\r\n {loading ? \"...\" : t(\"submit\")}\r\n </Button>\r\n </div>\r\n </div>\r\n </DialogContent>\r\n </Dialog>\r\n );\r\n}\r\n"],"mappings":"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","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FeatureRequestDialog.d.ts","sourceRoot":"","sources":["../../../../src/core/components/support/FeatureRequestDialog.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"FeatureRequestDialog.d.ts","sourceRoot":"","sources":["../../../../src/core/components/support/FeatureRequestDialog.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAqF/B,KAAK,KAAK,GAAG;IACX,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,KAAK,qBA4GpF"}
|
|
@@ -13,6 +13,8 @@ import { Input } from "../ui/Input";
|
|
|
13
13
|
import { Textarea } from "../ui/Textarea";
|
|
14
14
|
import * as LR from "lucide-react";
|
|
15
15
|
import { toast } from "sonner";
|
|
16
|
+
import { useCurrentViewerPosition } from "../../hooks/useCurrentViewerPosition";
|
|
17
|
+
import { useShareUrl } from "../../hooks/useShareUrl";
|
|
16
18
|
function getBrowserInfo() {
|
|
17
19
|
var _a, _b, _c, _d;
|
|
18
20
|
const ua = navigator.userAgent;
|
|
@@ -37,14 +39,37 @@ function getBrowserInfo() {
|
|
|
37
39
|
return "Unknown";
|
|
38
40
|
}
|
|
39
41
|
function getDeviceInfo() {
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
return
|
|
42
|
+
const ua = navigator.userAgent;
|
|
43
|
+
if (/iPhone/i.test(ua)) {
|
|
44
|
+
return "Mobile - iPhone";
|
|
45
|
+
}
|
|
46
|
+
if (/Android/i.test(ua) && /Mobile/i.test(ua)) {
|
|
47
|
+
return "Mobile - Android";
|
|
48
|
+
}
|
|
49
|
+
if (/iPad/i.test(ua)) {
|
|
50
|
+
return "Tablet - iPad";
|
|
51
|
+
}
|
|
52
|
+
if (/Android/i.test(ua) && !/Mobile/i.test(ua)) {
|
|
53
|
+
return "Tablet - Android";
|
|
43
54
|
}
|
|
44
|
-
|
|
55
|
+
if (/Windows NT/i.test(ua)) {
|
|
56
|
+
return "Desktop - Windows";
|
|
57
|
+
}
|
|
58
|
+
if (/Macintosh|Mac OS X/i.test(ua)) {
|
|
59
|
+
return "Desktop - macOS";
|
|
60
|
+
}
|
|
61
|
+
if (/CrOS/i.test(ua)) {
|
|
62
|
+
return "Desktop - ChromeOS";
|
|
63
|
+
}
|
|
64
|
+
if (/Linux/i.test(ua)) {
|
|
65
|
+
return "Desktop - Linux";
|
|
66
|
+
}
|
|
67
|
+
return "Unknown";
|
|
45
68
|
}
|
|
46
69
|
function FeatureRequestDialog({ open, onOpenChange, userEmail, viewer }) {
|
|
47
70
|
const t = useTranslations("supportDialog");
|
|
71
|
+
const getPosition = useCurrentViewerPosition();
|
|
72
|
+
const getShareUrl = useShareUrl();
|
|
48
73
|
const [title, setTitle] = React.useState("");
|
|
49
74
|
const [description, setDescription] = React.useState("");
|
|
50
75
|
const [loading, setLoading] = React.useState(false);
|
|
@@ -56,6 +81,7 @@ function FeatureRequestDialog({ open, onOpenChange, userEmail, viewer }) {
|
|
|
56
81
|
if (!title.trim() || !description.trim()) return;
|
|
57
82
|
try {
|
|
58
83
|
setLoading(true);
|
|
84
|
+
const shareUrl = await getShareUrl();
|
|
59
85
|
await fetch("/api/github-issue", {
|
|
60
86
|
method: "POST",
|
|
61
87
|
headers: { "Content-Type": "application/json" },
|
|
@@ -65,13 +91,14 @@ function FeatureRequestDialog({ open, onOpenChange, userEmail, viewer }) {
|
|
|
65
91
|
type: "feature-request",
|
|
66
92
|
labels: ["users"],
|
|
67
93
|
meta: {
|
|
68
|
-
url:
|
|
94
|
+
url: shareUrl,
|
|
69
95
|
userAgent: navigator.userAgent,
|
|
70
96
|
browser: getBrowserInfo(),
|
|
71
97
|
device: getDeviceInfo(),
|
|
72
98
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
73
99
|
viewer,
|
|
74
|
-
userEmail
|
|
100
|
+
userEmail,
|
|
101
|
+
position: getPosition()
|
|
75
102
|
}
|
|
76
103
|
})
|
|
77
104
|
});
|