From 766fbe1e5de0af7e8190836c59a9f6554046d190 Mon Sep 17 00:00:00 2001 From: Blue Mouse Date: Tue, 7 Apr 2026 18:58:36 +0100 Subject: [PATCH 1/5] Move alerts to project settings --- .../docs/analytics-dashboard/alerts.mdx | 4 +- ...-analytics-dashboard-into-your-website.mdx | 4 +- .../docs/sitesettings/get-traffic-alerts.mdx | 7 +- web/app/lib/constants/index.ts | 1 - .../Alerts/ProjectAlertsSettings.tsx | 18 ++- .../Alerts/ProjectAlertsView.tsx | 120 +++--------------- .../Project/Settings/ProjectSettings.tsx | 23 ++++ .../Project/View/ViewProject.helpers.tsx | 5 +- web/app/pages/Project/View/ViewProject.tsx | 14 -- .../View/components/ProjectSidebar.tsx | 8 +- web/app/routes/projects.$id.tsx | 11 -- web/public/locales/en.json | 2 + 12 files changed, 62 insertions(+), 155 deletions(-) rename web/app/pages/Project/{tabs => Settings}/Alerts/ProjectAlertsSettings.tsx (98%) rename web/app/pages/Project/{tabs => Settings}/Alerts/ProjectAlertsView.tsx (83%) diff --git a/docs/content/docs/analytics-dashboard/alerts.mdx b/docs/content/docs/analytics-dashboard/alerts.mdx index f14c89d2c..35bcb058b 100644 --- a/docs/content/docs/analytics-dashboard/alerts.mdx +++ b/docs/content/docs/analytics-dashboard/alerts.mdx @@ -31,7 +31,7 @@ To configure these, go to your **Account Settings** and follow the instructions ## Creating an Alert -1. Navigate to your project's dashboard and click on the **Alerts** tab. +1. Navigate to your project's **Settings** and click on the **Alerts** tab. 2. Click the **Add Alert** button. 3. Configure the alert settings: - **Name**: A descriptive name for your alert (e.g., "High Traffic Warning"). @@ -54,7 +54,7 @@ You can create alerts based on the following metrics: ## Managing Alerts -Once created, your alerts will appear in a list on the Alerts tab. +Once created, your alerts will appear in a list on the Alerts tab in your project settings. - **Edit**: Click the **Pencil** icon to modify the alert's threshold or settings. - **Delete**: Click the **Trash** icon to remove the alert permanently. diff --git a/docs/content/docs/sitesettings/embed-your-analytics-dashboard-into-your-website.mdx b/docs/content/docs/sitesettings/embed-your-analytics-dashboard-into-your-website.mdx index 6ff1dd00a..8207ed49b 100644 --- a/docs/content/docs/sitesettings/embed-your-analytics-dashboard-into-your-website.mdx +++ b/docs/content/docs/sitesettings/embed-your-analytics-dashboard-into-your-website.mdx @@ -42,7 +42,7 @@ Here is a list of all available options: - `theme`: `light` or `dark` - the theme of the dashboard. - `lng`: language to display the dashboard in. Supported languages: `en`, `uk`, `pl`, `de`, `fr`. Defaults to user's browser language. -- `tab`: `traffic`, `performance`, `profiles`, `funnels`, `sessions`, `errors`, `goals`, `experiments`, `featureFlags`, `alerts`, `captcha`, `ai` - the default tab to show. By default, the traffic tab is shown. -- `tabs`: `traffic`, `performance`, `profiles`, `funnels`, `sessions`, `errors`, `goals`, `experiments`, `featureFlags`, `alerts`, `captcha`, `ai` - a comma-separated list of tabs that should be accessible in dashboard. Example: `tabs=traffic,performance,sessions` will only show the traffic, performance and sessions tabs. By default, all tabs are shown. +- `tab`: `traffic`, `performance`, `profiles`, `funnels`, `sessions`, `errors`, `goals`, `experiments`, `featureFlags`, `captcha`, `ai` - the default tab to show. By default, the traffic tab is shown. +- `tabs`: `traffic`, `performance`, `profiles`, `funnels`, `sessions`, `errors`, `goals`, `experiments`, `featureFlags`, `captcha`, `ai` - a comma-separated list of tabs that should be accessible in dashboard. Example: `tabs=traffic,performance,sessions` will only show the traffic, performance and sessions tabs. By default, all tabs are shown. - `embedded` - if set to `true`, header, footer and any marketing CTAs will be hidden. - `password` - if your dashboard is password-protected, you can pass the password as a query parameter. Example: `password=yourpassword`, this way the dashboard will be unlocked without the need to enter the password. diff --git a/docs/content/docs/sitesettings/get-traffic-alerts.mdx b/docs/content/docs/sitesettings/get-traffic-alerts.mdx index 855d5551c..3f75ebc00 100644 --- a/docs/content/docs/sitesettings/get-traffic-alerts.mdx +++ b/docs/content/docs/sitesettings/get-traffic-alerts.mdx @@ -22,8 +22,9 @@ After you connected your integration, all the alerts will be sent there. You can To set up an alert, 1. Go to your website's analytics page. -2. Navigate to the "Alerts" tab. -3. Click on the "Add alert" button. +2. Open the project **Settings** (gear icon in the sidebar). +3. Navigate to the **Alerts** tab. +4. Click on the "Add alert" button. Alerts screen @@ -36,4 +37,4 @@ When you click on the "Add alert" button, you will see a screen where you can cr - when the number of users on your website is less than 5 during the last 24 hours - ... and more -From the same "Alerts" tab you can also see the list of all the alerts you have set up and the last time they triggered. You can edit or delete them by clicking on the "Edit" or "Delete" buttons. +From the same "Alerts" tab in project settings you can also see the list of all the alerts you have set up and the last time they triggered. You can edit or delete them by clicking on the "Edit" or "Delete" buttons. diff --git a/web/app/lib/constants/index.ts b/web/app/lib/constants/index.ts index 233e4c746..a5a761ad1 100644 --- a/web/app/lib/constants/index.ts +++ b/web/app/lib/constants/index.ts @@ -506,7 +506,6 @@ const PRODUCTION_PROJECT_TABS = { goals: 'goals', experiments: 'experiments', featureFlags: 'featureFlags', - alerts: 'alerts', captcha: 'captcha', ai: 'ai', } as const diff --git a/web/app/pages/Project/tabs/Alerts/ProjectAlertsSettings.tsx b/web/app/pages/Project/Settings/Alerts/ProjectAlertsSettings.tsx similarity index 98% rename from web/app/pages/Project/tabs/Alerts/ProjectAlertsSettings.tsx rename to web/app/pages/Project/Settings/Alerts/ProjectAlertsSettings.tsx index 23118ccaf..b71a9c5b0 100644 --- a/web/app/pages/Project/tabs/Alerts/ProjectAlertsSettings.tsx +++ b/web/app/pages/Project/Settings/Alerts/ProjectAlertsSettings.tsx @@ -389,17 +389,15 @@ const ProjectAlertsSettings = ({ showSearchButton={false} showRefreshButton={false} showPeriodSelector={false} - leftContent={ - - {title} - - } /> + + {title} + { const { t } = useTranslation('common') @@ -255,56 +242,29 @@ const AlertRow = ({ ) } -interface DeferredAlertsData { - alertsData: AlertsResponse | null -} - -function AlertsDataResolver({ - children, -}: { - children: (data: DeferredAlertsData) => React.ReactNode -}) { - const { alertsData: alertsDataPromise } = useLoaderData() - const alertsData = alertsDataPromise ? use(alertsDataPromise) : null - return <>{children({ alertsData })} -} - -function ProjectAlertsWrapper() { - return ( - }> - - {(deferredData) => } - - - ) -} - -interface ProjectAlertsInnerProps { - deferredData: DeferredAlertsData +interface ProjectAlertsProps { + projectId: string + projectRole?: string } -const ProjectAlertsInner = ({ deferredData }: ProjectAlertsInnerProps) => { - const { id, project } = useCurrentProject() - const revalidator = useRevalidator() +const ProjectAlertsInner = ({ projectId, projectRole }: ProjectAlertsProps) => { + const id = projectId const { t } = useTranslation() const { user, isAuthenticated } = useAuth() const [isPaidFeatureOpened, setIsPaidFeatureOpened] = useState(false) const [searchParams, setSearchParams] = useSearchParams() - const isEmbedded = searchParams.get('embedded') === 'true' const fetcher = useFetcher() const lastHandledData = useRef(null) // Track if we're in pagination mode const [isSearchMode, setIsSearchMode] = useState(false) - const [total, setTotal] = useState(() => deferredData.alertsData?.total || 0) - const [alerts, setAlerts] = useState( - () => deferredData.alertsData?.results || [], - ) + const [total, setTotal] = useState(0) + const [alerts, setAlerts] = useState([]) const [page, setPage] = useState(1) const [error, setError] = useState(null) - const isLoading = revalidator.state === 'loading' || fetcher.state !== 'idle' + const isLoading = fetcher.state !== 'idle' const pageAmount = Math.ceil(total / DEFAULT_ALERTS_TAKE) @@ -312,7 +272,7 @@ const ProjectAlertsInner = ({ deferredData }: ProjectAlertsInnerProps) => { const isLimitReached = isAuthenticated && total >= limits?.maxAlerts // Check if user has permission to view alerts - const canManageAlerts = project?.role === 'owner' && isAuthenticated + const canManageAlerts = projectRole === 'owner' && isAuthenticated // Get active alert from URL params const activeAlertId = searchParams.get('alertId') @@ -326,18 +286,6 @@ const ProjectAlertsInner = ({ deferredData }: ProjectAlertsInnerProps) => { return newSearchParams.toString() }, [searchParams]) - // Sync state when loader provides new data - useEffect(() => { - if ( - deferredData.alertsData && - revalidator.state === 'idle' && - !isSearchMode - ) { - setAlerts(deferredData.alertsData.results || []) - setTotal(deferredData.alertsData.total || 0) - } - }, [revalidator.state, deferredData.alertsData, isSearchMode]) - const loadAlerts = (take: number, skip: number) => { if (fetcher.state !== 'idle') return setIsSearchMode(true) @@ -363,18 +311,13 @@ const ProjectAlertsInner = ({ deferredData }: ProjectAlertsInnerProps) => { setTotal(result.total) } else if (intent === 'delete-alert') { toast.success(t('alertsSettings.alertDeleted')) - if (page === 1) { - setIsSearchMode(false) - revalidator.revalidate() - } else { - loadAlerts(DEFAULT_ALERTS_TAKE, (page - 1) * DEFAULT_ALERTS_TAKE) - } + loadAlerts(DEFAULT_ALERTS_TAKE, (page - 1) * DEFAULT_ALERTS_TAKE) } } else if (fetcherError) { setError(fetcherError) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fetcher.state, fetcher.data, t, page, revalidator]) + }, [fetcher.state, fetcher.data, t, page]) // Handle page changes - use fetcher for pagination useEffect(() => { @@ -385,23 +328,15 @@ const ProjectAlertsInner = ({ deferredData }: ProjectAlertsInnerProps) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [page, canManageAlerts]) - // Fallback load for client-side tab switches where deferred data is not present yet. useEffect(() => { if (!canManageAlerts) return - if (deferredData.alertsData) return if (isSearchMode) return if (fetcher.state !== 'idle') return if (!_isEmpty(alerts)) return loadAlerts(DEFAULT_ALERTS_TAKE, 0) // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - canManageAlerts, - deferredData.alertsData, - isSearchMode, - fetcher.state, - alerts, - ]) + }, [canManageAlerts, isSearchMode, fetcher.state, alerts]) const queryMetricTMapping: Record = useMemo(() => { const values = _values(QUERY_METRIC) @@ -454,13 +389,7 @@ const ProjectAlertsInner = ({ deferredData }: ProjectAlertsInnerProps) => { const handleAlertSaved = () => { closeAlertSettings() - // Reload alerts after saving - if (page === 1) { - setIsSearchMode(false) - revalidator.revalidate() - } else { - loadAlerts(DEFAULT_ALERTS_TAKE, (page - 1) * DEFAULT_ALERTS_TAKE) - } + loadAlerts(DEFAULT_ALERTS_TAKE, (page - 1) * DEFAULT_ALERTS_TAKE) } const onDelete = (alertId: string) => { @@ -474,12 +403,7 @@ const ProjectAlertsInner = ({ deferredData }: ProjectAlertsInnerProps) => { if (!canManageAlerts) { return ( -
+
@@ -534,15 +458,9 @@ const ProjectAlertsInner = ({ deferredData }: ProjectAlertsInnerProps) => { return ( <> - {isLoading && !_isEmpty(alerts) ? : null}
{_isEmpty(alerts) ? ( -
+
@@ -609,6 +527,6 @@ const ProjectAlertsInner = ({ deferredData }: ProjectAlertsInnerProps) => { ) } -const ProjectAlerts = ProjectAlertsWrapper +const ProjectAlerts = ProjectAlertsInner export default ProjectAlerts diff --git a/web/app/pages/Project/Settings/ProjectSettings.tsx b/web/app/pages/Project/Settings/ProjectSettings.tsx index 6977b7585..897b32323 100644 --- a/web/app/pages/Project/Settings/ProjectSettings.tsx +++ b/web/app/pages/Project/Settings/ProjectSettings.tsx @@ -22,6 +22,7 @@ import { ShieldCheckIcon, CurrencyDollarIcon, DownloadIcon, + BellRingingIcon, } from '@phosphor-icons/react' import React, { useState, useEffect, useMemo, useRef } from 'react' import { Trans, useTranslation } from 'react-i18next' @@ -65,6 +66,7 @@ import DangerZone from './tabs/DangerZone' import General from './tabs/General' import Revenue from './tabs/Revenue' import Shields from './tabs/Shields' +import ProjectAlerts from './Alerts/ProjectAlertsView' import DataImportTab from './components/DataImportTab' const MAX_NAME_LENGTH = 50 @@ -353,6 +355,7 @@ const ProjectSettings = () => { | 'access' | 'captcha' | 'integrations' + | 'alerts' | 'revenue' | 'emails' | 'people' @@ -403,6 +406,14 @@ const ProjectSettings = () => { iconColor: 'text-purple-500', visible: !isSelfhosted, }, + { + id: 'alerts', + label: t('project.settings.tabs.alerts'), + description: t('project.settings.tabs.alertsDesc'), + icon: BellRingingIcon, + iconColor: 'text-cyan-500', + visible: !isSelfhosted, + }, { id: 'revenue', label: t('project.settings.tabs.revenue'), @@ -1225,6 +1236,18 @@ const ProjectSettings = () => {
) : null} + {activeTab === 'alerts' && activeTabConfig ? ( + <> + + + + ) : null} + {activeTab === 'captcha' && activeTabConfig ? (
{ label: t('dashboard.askAi'), icon: SparkleIcon, }, - { - id: PROJECT_TABS.alerts, - label: t('dashboard.alerts'), - icon: BellRingingIcon, - }, { id: PROJECT_TABS.experiments, label: t('dashboard.experiments'), @@ -1126,10 +1119,6 @@ const ViewProjectContent = () => { } if (pair.period === 'compare') { - if (activeTab === PROJECT_TABS.alerts) { - return - } - if (isActiveCompare) { compareDisable() } else { @@ -1535,9 +1524,6 @@ const ViewProjectContent = () => { {activeTab === PROJECT_TABS.funnels ? ( ) : null} - {activeTab === PROJECT_TABS.alerts ? ( - - ) : null} {activeTab === PROJECT_TABS.profiles ? ( ) : null} diff --git a/web/app/pages/Project/View/components/ProjectSidebar.tsx b/web/app/pages/Project/View/components/ProjectSidebar.tsx index 3277420b6..2163bcee9 100644 --- a/web/app/pages/Project/View/components/ProjectSidebar.tsx +++ b/web/app/pages/Project/View/components/ProjectSidebar.tsx @@ -64,7 +64,6 @@ const ICON_COLORS: Record = { traffic: 'text-blue-500', performance: 'text-amber-500', funnels: 'text-teal-500', - alerts: 'text-cyan-500', // Product Analytics profiles: 'text-fuchsia-500', sessions: 'text-indigo-500', @@ -399,12 +398,7 @@ const ProjectSidebar: React.FC = ({ const groups: TabGroup[] = [] const webAnalyticsTabs = tabs.filter((tab) => - [ - PROJECT_TABS.traffic, - PROJECT_TABS.performance, - PROJECT_TABS.funnels, - PROJECT_TABS.alerts, - ] + [PROJECT_TABS.traffic, PROJECT_TABS.performance, PROJECT_TABS.funnels] .filter(Boolean) .includes(tab.id as any), ) diff --git a/web/app/routes/projects.$id.tsx b/web/app/routes/projects.$id.tsx index e724719ea..d861e90f9 100644 --- a/web/app/routes/projects.$id.tsx +++ b/web/app/routes/projects.$id.tsx @@ -25,7 +25,6 @@ import { getErrorOverviewServer, getProjectFeatureFlagsServer, getProjectGoalsServer, - getProjectAlertsServer, getFunnelDataServer, type TrafficLogResponse, type OverallObject, @@ -38,7 +37,6 @@ import { type ErrorOverviewResponse, type FeatureFlagsResponse, type GoalsResponse, - type AlertsResponse, type FunnelDataResponse, type AnalyticsFilter, } from '~/api/api.server' @@ -175,7 +173,6 @@ export const meta: MetaFunction = ({ data, location }) => { [PROJECT_TABS.featureFlags]: t('dashboard.featureFlags'), [PROJECT_TABS.captcha]: t('common.captcha'), [PROJECT_TABS.ai]: t('dashboard.askAi'), - [PROJECT_TABS.alerts]: t('dashboard.alerts'), [PROJECT_TABS.experiments]: t('dashboard.experiments'), settings: t('common.settings'), } @@ -310,8 +307,6 @@ export interface ProjectLoaderData { featureFlagsData?: Promise // Goals data goalsData?: Promise - // Alerts data - alertsData?: Promise // Funnels data funnelData?: Promise // Data import @@ -431,7 +426,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { let errorOverview: Promise | undefined let featureFlagsData: Promise | undefined let goalsData: Promise | undefined - let alertsData: Promise | undefined let funnelData: Promise | undefined let hasImportedData: Promise | undefined @@ -535,10 +529,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { goalsData = getProjectGoalsServer(request, projectId).then( (res) => res.data, ) - } else if (tab === PROJECT_TABS.alerts) { - alertsData = getProjectAlertsServer(request, projectId).then( - (res) => res.data, - ) } else if (tab === PROJECT_TABS.funnels) { // Fetch funnel data if funnelId is in URL const funnelId = url.searchParams.get('funnelId') @@ -591,7 +581,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { errorOverview, featureFlagsData, goalsData, - alertsData, funnelData, hasImportedData, }, diff --git a/web/public/locales/en.json b/web/public/locales/en.json index da9bbb826..a6b4abcbd 100644 --- a/web/public/locales/en.json +++ b/web/public/locales/en.json @@ -1734,6 +1734,8 @@ "dangerDesc": "Destructive actions that cannot be undone.", "integrations": "Integrations", "integrationsDesc": "Connect external services to enrich your analytics.", + "alerts": "Alerts", + "alertsDesc": "Get notified about traffic spikes, errors, and custom events.", "revenue": "Revenue", "revenueDesc": "Track revenue and attribute sales to campaigns.", "import": "Import data", From 3f355cec2e3c50ed6721cf8312dd25239205b0a7 Mon Sep 17 00:00:00 2001 From: Blue Mouse Date: Tue, 7 Apr 2026 21:22:29 +0100 Subject: [PATCH 2/5] fix: Loading state --- web/app/pages/Project/Settings/Alerts/ProjectAlertsView.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/app/pages/Project/Settings/Alerts/ProjectAlertsView.tsx b/web/app/pages/Project/Settings/Alerts/ProjectAlertsView.tsx index a20c0eb33..42779e50f 100644 --- a/web/app/pages/Project/Settings/Alerts/ProjectAlertsView.tsx +++ b/web/app/pages/Project/Settings/Alerts/ProjectAlertsView.tsx @@ -259,7 +259,7 @@ const ProjectAlertsInner = ({ projectId, projectRole }: ProjectAlertsProps) => { // Track if we're in pagination mode const [isSearchMode, setIsSearchMode] = useState(false) const [total, setTotal] = useState(0) - const [alerts, setAlerts] = useState([]) + const [alerts, setAlerts] = useState(null) const [page, setPage] = useState(1) const [error, setError] = useState(null) @@ -332,7 +332,7 @@ const ProjectAlertsInner = ({ projectId, projectRole }: ProjectAlertsProps) => { if (!canManageAlerts) return if (isSearchMode) return if (fetcher.state !== 'idle') return - if (!_isEmpty(alerts)) return + if (alerts !== null) return loadAlerts(DEFAULT_ALERTS_TAKE, 0) // eslint-disable-next-line react-hooks/exhaustive-deps @@ -459,7 +459,7 @@ const ProjectAlertsInner = ({ projectId, projectRole }: ProjectAlertsProps) => { return ( <>
- {_isEmpty(alerts) ? ( + {alerts === null ? null : alerts.length === 0 ? (
From eb7a1f42563d5e31a7bd47d57a07479bf0a8bc71 Mon Sep 17 00:00:00 2001 From: Blue Mouse Date: Tue, 7 Apr 2026 21:23:47 +0100 Subject: [PATCH 3/5] fix: Pagination --- .../pages/Project/Settings/Alerts/ProjectAlertsView.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/web/app/pages/Project/Settings/Alerts/ProjectAlertsView.tsx b/web/app/pages/Project/Settings/Alerts/ProjectAlertsView.tsx index 42779e50f..fedd24977 100644 --- a/web/app/pages/Project/Settings/Alerts/ProjectAlertsView.tsx +++ b/web/app/pages/Project/Settings/Alerts/ProjectAlertsView.tsx @@ -311,7 +311,14 @@ const ProjectAlertsInner = ({ projectId, projectRole }: ProjectAlertsProps) => { setTotal(result.total) } else if (intent === 'delete-alert') { toast.success(t('alertsSettings.alertDeleted')) - loadAlerts(DEFAULT_ALERTS_TAKE, (page - 1) * DEFAULT_ALERTS_TAKE) + + if (page > 1 && alerts?.length === 1) { + const newPage = page - 1 + setPage(newPage) + loadAlerts(DEFAULT_ALERTS_TAKE, (newPage - 1) * DEFAULT_ALERTS_TAKE) + } else { + loadAlerts(DEFAULT_ALERTS_TAKE, (page - 1) * DEFAULT_ALERTS_TAKE) + } } } else if (fetcherError) { setError(fetcherError) From f7ad064e263724cdd85727343117341e5d2b6248 Mon Sep 17 00:00:00 2001 From: Blue Mouse Date: Tue, 7 Apr 2026 22:36:23 +0100 Subject: [PATCH 4/5] fix: Only show alerts to project owners --- web/app/pages/Project/Settings/ProjectSettings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/pages/Project/Settings/ProjectSettings.tsx b/web/app/pages/Project/Settings/ProjectSettings.tsx index 897b32323..a70015d6e 100644 --- a/web/app/pages/Project/Settings/ProjectSettings.tsx +++ b/web/app/pages/Project/Settings/ProjectSettings.tsx @@ -412,7 +412,7 @@ const ProjectSettings = () => { description: t('project.settings.tabs.alertsDesc'), icon: BellRingingIcon, iconColor: 'text-cyan-500', - visible: !isSelfhosted, + visible: !isSelfhosted && project?.role === 'owner', }, { id: 'revenue', From 6fddf9d2c13613f5ebd92184a2376a469864ee44 Mon Sep 17 00:00:00 2001 From: Blue Mouse Date: Tue, 7 Apr 2026 23:00:00 +0100 Subject: [PATCH 5/5] fix knip --- web/app/api/api.server.ts | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/web/app/api/api.server.ts b/web/app/api/api.server.ts index 0b3452878..ed0fb78bf 100644 --- a/web/app/api/api.server.ts +++ b/web/app/api/api.server.ts @@ -1,4 +1,3 @@ -import { Alerts } from '~/lib/models/Alerts' import { Auth } from '~/lib/models/Auth' import { User } from '~/lib/models/User' import { @@ -1419,32 +1418,6 @@ export async function getGoalChartServer( ) } -// ============================================================================ -// MARK: Alerts API (Deferred Loading) -// ============================================================================ - -export interface AlertsResponse { - results: Alerts[] - total: number -} - -export async function getProjectAlertsServer( - request: Request, - projectId: string, - take = 25, - skip = 0, -): Promise> { - const params = new URLSearchParams({ - take: String(take), - skip: String(skip), - }) - - return serverFetch( - request, - `alert/project/${projectId}?${params.toString()}`, - ) -} - // ============================================================================ // MARK: Funnels API (Deferred Loading) // ============================================================================