Voeg Sentry-foutregistratie toe als gekozen logging-bestemming
Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
@@ -93,6 +93,12 @@ jobs:
|
|||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm run build
|
run: pnpm run build
|
||||||
|
env:
|
||||||
|
# Sentry DSN is niet gevoelig (veilig om in de client-bundle te zitten),
|
||||||
|
# daarom een Gitea Actions "vars"-waarde i.p.v. een secret. Optioneel:
|
||||||
|
# als deze niet is ingesteld, wordt Sentry-logging simpelweg overgeslagen
|
||||||
|
# (zie src/main.tsx) en blijft alleen console-logging actief.
|
||||||
|
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
|
||||||
|
|
||||||
- name: Upload build artifact
|
- name: Upload build artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
|
|||||||
@@ -456,3 +456,15 @@
|
|||||||
**Context**: Post-workflow follow-up (feature already marked Complete); clarifies wording of the previous follow-up's path-discrepancy note per user feedback. Changes not yet committed/pushed.
|
**Context**: Post-workflow follow-up (feature already marked Complete); clarifies wording of the previous follow-up's path-discrepancy note per user feedback. Changes not yet committed/pushed.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Post-Completion Follow-up: Client-Side Error Logging Destination Decided (Sentry)
|
||||||
|
|
||||||
|
**Timestamp**: 2026-07-25T12:55:00Z
|
||||||
|
|
||||||
|
**User Input**: "Als het goed is stonden er nog een paar punten open? Zullen we het volgende punt oppakken? Kan je ook een feature-branch aanmaken met een passende naam? [...]" — followed by selecting "Logging-bestemming kiezen" and then "Sentry (free tier)" as the concrete destination.
|
||||||
|
|
||||||
|
**AI Response**: "Picked up the next open follow-up from `production-readiness-checklist.md`: the client-side error logging destination. Created feature branch `feature/sentry_error_logging` (carrying over the previously uncommitted path-documentation changes, committed there first). Added `@sentry/react` as a dependency, initialized it in `src/main.tsx` via `Sentry.init({ dsn: import.meta.env.VITE_SENTRY_DSN })` (no-op if the DSN is unset), added `src/vite-env.d.ts` for `import.meta.env` typing, and hooked `ErrorBoundary.componentDidCatch` to also call `Sentry.captureException`. Wired the DSN as a Gitea Actions repository variable (`vars.VITE_SENTRY_DSN`, not a secret) into the `Build` step of `continuous_integration.yaml`. Updated `monitoring-plan.md`, `monitoring-setup.md`, and `production-readiness-checklist.md` to mark this open item resolved, with the remaining manual step (creating the actual `VITE_SENTRY_DSN` Gitea variable with a real Sentry DSN) called out explicitly."
|
||||||
|
|
||||||
|
**Context**: Post-workflow follow-up (feature already marked Complete); resolves open follow-up #2 from `production-readiness-checklist.md`. Work done on new branch `feature/sentry_error_logging`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Initial answers to the monitoring plan were contradictory: Question 2 selected "
|
|||||||
## Chosen Approach(es)
|
## Chosen Approach(es)
|
||||||
|
|
||||||
### Logging
|
### Logging
|
||||||
Client-side errors (JavaScript crashes, broken links) should be logged, but the concrete destination is not yet decided (original Question 3 = C, "not yet determined"). This is tracked as an **open action item** below rather than blocking this stage.
|
Client-side errors (JavaScript crashes, broken links) are logged. **Decided**: Sentry free tier (in addition to the browser console) — see the follow-up in `audit.md` and the implementation in `monitoring-setup.md`.
|
||||||
|
|
||||||
### Dashboards
|
### Dashboards
|
||||||
A combination of:
|
A combination of:
|
||||||
@@ -27,7 +27,7 @@ A combination of:
|
|||||||
- **Reuse of existing infrastructure**: this feature does not plug into any pre-existing shared monitoring (original Question 7 = A) — there is none yet. Should a shared back-end/CMS monitoring stack be introduced later, this can be revisited.
|
- **Reuse of existing infrastructure**: this feature does not plug into any pre-existing shared monitoring (original Question 7 = A) — there is none yet. Should a shared back-end/CMS monitoring stack be introduced later, this can be revisited.
|
||||||
|
|
||||||
## Open Action Items
|
## Open Action Items
|
||||||
1. **Decide logging destination**: choose between "browser console only" (no central storage, manual debugging) or a free/low-cost external error-tracking service (e.g. Sentry free tier) once this becomes a priority. Until decided, `monitoring-setup.md` documents both options so either can be adopted without re-doing this stage.
|
1. ~~Decide logging destination~~ — **Resolved**: Sentry free tier, wired into `ErrorBoundary`/`main.tsx` (see `monitoring-setup.md`). The Gitea Actions variable `VITE_SENTRY_DSN` still needs to be created by the user with a real Sentry project DSN; until then the build simply skips Sentry initialization and only console-logging is active.
|
||||||
2. **Pick concrete analytics + uptime tools**: `monitoring-setup.md` lists candidate free-tier tools; final tool selection/account creation is a manual follow-up outside this workflow (no code changes required to swap providers, since neither is wired into the codebase yet beyond an optional embed snippet).
|
2. **Pick concrete analytics + uptime tools**: `monitoring-setup.md` lists candidate free-tier tools; final tool selection/account creation is a manual follow-up outside this workflow (no code changes required to swap providers, since neither is wired into the codebase yet beyond an optional embed snippet).
|
||||||
|
|
||||||
## Rationale
|
## Rationale
|
||||||
|
|||||||
@@ -9,16 +9,17 @@ Concrete setup steps for the approaches chosen in `monitoring-plan.md`: **Loggin
|
|||||||
- Broken/failed navigation (e.g. an unexpected router error).
|
- Broken/failed navigation (e.g. an unexpected router error).
|
||||||
- No user PII, form input, or sensitive data should ever be logged — this is a public marketing site, but keep this discipline regardless.
|
- No user PII, form input, or sensitive data should ever be logged — this is a public marketing site, but keep this discipline regardless.
|
||||||
|
|
||||||
### Destination — open decision
|
### Destination — decided: Sentry free tier + console
|
||||||
The destination was not finalized (original Question 3 = C). Two supported options, either of which can be adopted later without further design work:
|
Both the console and Sentry are now active (original Question 3 resolved as a combination):
|
||||||
|
|
||||||
**Option 1: Browser console only (default today)**
|
**Console (always on)**
|
||||||
- No code changes needed — errors already surface via `console.error` inside the existing `ErrorBoundary`.
|
- Errors already surface via `console.error` inside `ErrorBoundary.componentDidCatch` — unchanged, zero cost, useful for local/manual debugging.
|
||||||
- Zero cost, but not centrally visible; only useful for manual debugging (e.g. via a user's screenshot or a support request).
|
|
||||||
|
|
||||||
**Option 2: External error-tracking service (e.g. Sentry free tier)**
|
**Sentry free tier (implemented)**
|
||||||
- When decided, add `@sentry/react` as a dependency, initialize it once in the app entry point (e.g. `src/main.tsx`) with the project DSN, and report caught errors from the `ErrorBoundary`'s `componentDidCatch`/`onError` hook to Sentry in addition to the console.
|
- `@sentry/react` is a dependency; `src/main.tsx` calls `Sentry.init({ dsn: import.meta.env.VITE_SENTRY_DSN })` at startup, but only when a DSN is present — if not configured, Sentry is silently skipped and only console-logging remains active (safe default, no crash on missing config).
|
||||||
- Store the DSN as a Gitea Actions variable (or a build-time `.env` value, since it's not a secret — Sentry DSNs are safe to expose client-side) and inject it via Vite's `import.meta.env`.
|
- `ErrorBoundary.componentDidCatch` calls `Sentry.captureException(error, { extra: { componentStack: info.componentStack } })` in addition to `console.error`.
|
||||||
|
- The DSN is injected at build time via Vite's `import.meta.env.VITE_SENTRY_DSN` (typed in `src/vite-env.d.ts`). It is **not** a secret (Sentry DSNs are safe to expose client-side), so it is passed as a **Gitea Actions repository variable** (`vars.VITE_SENTRY_DSN`, not a secret) to the `Build` step in `continuous_integration.yaml`.
|
||||||
|
- **Manual follow-up required**: create a free Sentry project (https://sentry.io) for this app, copy its DSN, and set it as the `VITE_SENTRY_DSN` repository variable in Gitea (Repository Settings → Actions → Variables). Until that variable is set, the build still succeeds and the site still works — Sentry reporting simply stays inactive.
|
||||||
- Free tier limits (error volume, retention) are typically sufficient for a low-traffic marketing site.
|
- Free tier limits (error volume, retention) are typically sufficient for a low-traffic marketing site.
|
||||||
|
|
||||||
**Log level strategy**: only errors are logged (no verbose/info-level client logging) — this is a static site with no meaningful "business events" beyond page views, which are covered by analytics (see Dashboards below), not logging.
|
**Log level strategy**: only errors are logged (no verbose/info-level client logging) — this is a static site with no meaningful "business events" beyond page views, which are covered by analytics (see Dashboards below), not logging.
|
||||||
@@ -55,7 +56,7 @@ Pick one:
|
|||||||
|
|
||||||
| Concern | Approach | Status |
|
| Concern | Approach | Status |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Client-side errors | Logging (console today; Sentry free tier optional later) | Destination open item |
|
| Client-side errors | Logging (console + Sentry free tier) | Implemented; `VITE_SENTRY_DSN` Gitea variable still needs to be created by the user |
|
||||||
| Visitor/usage insight | Analytics dashboard (Plausible/Umami/GA4) | Tool selection open item |
|
| Visitor/usage insight | Analytics dashboard (Plausible/Umami/GA4) | Tool selection open item |
|
||||||
| Site reachability | Uptime dashboard (UptimeRobot/Better Uptime) | Tool selection + production URL open item |
|
| Site reachability | Uptime dashboard (UptimeRobot/Better Uptime) | Tool selection + production URL open item |
|
||||||
| Alerting | Out of scope | Not configured |
|
| Alerting | Out of scope | Not configured |
|
||||||
|
|||||||
@@ -19,6 +19,6 @@
|
|||||||
- **Ready for Production**: Yes with caveats
|
- **Ready for Production**: Yes with caveats
|
||||||
- **Open Follow-ups**:
|
- **Open Follow-ups**:
|
||||||
- Finalize the hosting/domain setup and extend `.gitea/workflows/deploy.yml`'s `deploy` job to actually upload `dist/` to the host, instead of only packaging it as a downloadable artifact (see `deployment-plan.md` "Open Item")
|
- Finalize the hosting/domain setup and extend `.gitea/workflows/deploy.yml`'s `deploy` job to actually upload `dist/` to the host, instead of only packaging it as a downloadable artifact (see `deployment-plan.md` "Open Item")
|
||||||
- Decide the client-side error logging destination (browser console only vs. an external service such as Sentry free tier) — see `monitoring-setup.md`
|
- ~~Decide the client-side error logging destination~~ — **Resolved**: console + Sentry free tier, implemented (see `monitoring-setup.md`); remaining manual step is creating the `VITE_SENTRY_DSN` Gitea Actions variable
|
||||||
- Pick and configure the concrete analytics tool (e.g. Plausible/Umami/GA4) and uptime dashboard tool (e.g. UptimeRobot/Better Uptime), including registering the final production URL once hosting is finalized
|
- Pick and configure the concrete analytics tool (e.g. Plausible/Umami/GA4) and uptime dashboard tool (e.g. UptimeRobot/Better Uptime), including registering the final production URL once hosting is finalized
|
||||||
- Run a formal Lighthouse performance check before the first real production deployment (flagged as not yet run in `build-and-test-summary.md`)
|
- Run a formal Lighthouse performance check before the first real production deployment (flagged as not yet run in `build-and-test-summary.md`)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"@fontsource/instrument-sans": "^5.1.0",
|
"@fontsource/instrument-sans": "^5.1.0",
|
||||||
"@fontsource/jetbrains-mono": "^5.1.1",
|
"@fontsource/jetbrains-mono": "^5.1.1",
|
||||||
"@fontsource/sora": "^5.1.0",
|
"@fontsource/sora": "^5.1.0",
|
||||||
|
"@sentry/react": "^10.68.0",
|
||||||
"@tanstack/react-query": "^5.59.0",
|
"@tanstack/react-query": "^5.59.0",
|
||||||
"@tanstack/react-router": "^1.79.0",
|
"@tanstack/react-router": "^1.79.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
|||||||
Generated
+78
@@ -17,6 +17,9 @@ importers:
|
|||||||
'@fontsource/sora':
|
'@fontsource/sora':
|
||||||
specifier: ^5.1.0
|
specifier: ^5.1.0
|
||||||
version: 5.2.8
|
version: 5.2.8
|
||||||
|
'@sentry/react':
|
||||||
|
specifier: ^10.68.0
|
||||||
|
version: 10.68.0(react@18.3.1)
|
||||||
'@tanstack/react-query':
|
'@tanstack/react-query':
|
||||||
specifier: ^5.59.0
|
specifier: ^5.59.0
|
||||||
version: 5.101.2(react@18.3.1)
|
version: 5.101.2(react@18.3.1)
|
||||||
@@ -592,6 +595,40 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
|
'@sentry/browser-utils@10.68.0':
|
||||||
|
resolution: {integrity: sha512-be8VtdjCngKc77cstJeV+gO15iH+blyXpBDk8yOehmtX4BkFO33mfTMNCWVR2LA0oOxjIHWRAhf77fIUEhzxPg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@sentry/browser@10.68.0':
|
||||||
|
resolution: {integrity: sha512-8xVgk7oG2lajXnbXF6a7H1xMZ/U6icqSldHGzQu1+bajfrK8Gan9ULG/Xsj1VM1LlNeK6/7znDJ3u1jgvIwznw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@sentry/conventions@0.16.0':
|
||||||
|
resolution: {integrity: sha512-fO9PLmHdVURcSPUpWCItWAtgKiMwGdJHbovoSEyLplX5sxs2ugvI4CBPTrkkgqhObnZOD0CnWBKDzSVQYBKEyQ==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
|
'@sentry/core@10.68.0':
|
||||||
|
resolution: {integrity: sha512-5Amhx8ltVz7vb1bRGyf3c4J69/iHW8R/H+SJxTRILHlsSOBrnVVc/IQEYDC6PTRdRdZ3x2u7RVjxZi2Mhe525g==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@sentry/feedback@10.68.0':
|
||||||
|
resolution: {integrity: sha512-XbdcXiBnpC3vgw46eHOPeD/ZQ+XzluP75ubdUcaPDW02hCh2nsdXiwjZ2DBImbpvIpTbJgjHf/sIlHWvcZJ2Mg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@sentry/react@10.68.0':
|
||||||
|
resolution: {integrity: sha512-rIq4QR4ScMHHx9JJZv7Jgw31bMdUVJMx+ykHIJb7htjY6mj78sjKs+KpCsMDnvJxDhSmvftGM1KfKD4BggL7OQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.14.0 || 17.x || 18.x || 19.x
|
||||||
|
|
||||||
|
'@sentry/replay-canvas@10.68.0':
|
||||||
|
resolution: {integrity: sha512-HusYcr+He+ohnUDHunYrc5St6vdDnBXpUAndnT5ReyUMVSCiWKfY3paXowU/0787HwYfxdcpZgwC5u79+XbEIg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@sentry/replay@10.68.0':
|
||||||
|
resolution: {integrity: sha512-ZoG2n16vbkx4GWSCnLIqUUN9xlUmccQFbQ2US2rhruQeHTUnHl/ukr8NHOQXZaEbwKyMkX9bEMwfmZHJm+wSTQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
'@tanstack/history@1.162.0':
|
'@tanstack/history@1.162.0':
|
||||||
resolution: {integrity: sha512-79pf/RkhteYZTRgcR4F9kbk84P2N8rugQJswxfIqovlbRiT3yI7eBE+5QorIrZaOKktsgzRlXh1l/du/xpl4iA==}
|
resolution: {integrity: sha512-79pf/RkhteYZTRgcR4F9kbk84P2N8rugQJswxfIqovlbRiT3yI7eBE+5QorIrZaOKktsgzRlXh1l/du/xpl4iA==}
|
||||||
engines: {node: '>=20.19'}
|
engines: {node: '>=20.19'}
|
||||||
@@ -2280,6 +2317,47 @@ snapshots:
|
|||||||
'@rollup/rollup-win32-x64-msvc@4.62.2':
|
'@rollup/rollup-win32-x64-msvc@4.62.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@sentry/browser-utils@10.68.0':
|
||||||
|
dependencies:
|
||||||
|
'@sentry/conventions': 0.16.0
|
||||||
|
'@sentry/core': 10.68.0
|
||||||
|
|
||||||
|
'@sentry/browser@10.68.0':
|
||||||
|
dependencies:
|
||||||
|
'@sentry/browser-utils': 10.68.0
|
||||||
|
'@sentry/conventions': 0.16.0
|
||||||
|
'@sentry/core': 10.68.0
|
||||||
|
'@sentry/feedback': 10.68.0
|
||||||
|
'@sentry/replay': 10.68.0
|
||||||
|
'@sentry/replay-canvas': 10.68.0
|
||||||
|
|
||||||
|
'@sentry/conventions@0.16.0': {}
|
||||||
|
|
||||||
|
'@sentry/core@10.68.0':
|
||||||
|
dependencies:
|
||||||
|
'@sentry/conventions': 0.16.0
|
||||||
|
|
||||||
|
'@sentry/feedback@10.68.0':
|
||||||
|
dependencies:
|
||||||
|
'@sentry/core': 10.68.0
|
||||||
|
|
||||||
|
'@sentry/react@10.68.0(react@18.3.1)':
|
||||||
|
dependencies:
|
||||||
|
'@sentry/browser': 10.68.0
|
||||||
|
'@sentry/conventions': 0.16.0
|
||||||
|
'@sentry/core': 10.68.0
|
||||||
|
react: 18.3.1
|
||||||
|
|
||||||
|
'@sentry/replay-canvas@10.68.0':
|
||||||
|
dependencies:
|
||||||
|
'@sentry/core': 10.68.0
|
||||||
|
'@sentry/replay': 10.68.0
|
||||||
|
|
||||||
|
'@sentry/replay@10.68.0':
|
||||||
|
dependencies:
|
||||||
|
'@sentry/browser-utils': 10.68.0
|
||||||
|
'@sentry/core': 10.68.0
|
||||||
|
|
||||||
'@tanstack/history@1.162.0': {}
|
'@tanstack/history@1.162.0': {}
|
||||||
|
|
||||||
'@tanstack/query-core@5.101.2': {}
|
'@tanstack/query-core@5.101.2': {}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import type { ErrorInfo, ReactNode } from 'react';
|
import type { ErrorInfo, ReactNode } from 'react';
|
||||||
|
import * as Sentry from '@sentry/react';
|
||||||
|
|
||||||
interface ErrorBoundaryProps {
|
interface ErrorBoundaryProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@@ -22,6 +23,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
|||||||
|
|
||||||
componentDidCatch(error: Error, info: ErrorInfo): void {
|
componentDidCatch(error: Error, info: ErrorInfo): void {
|
||||||
console.error('Unexpected application error', error, info);
|
console.error('Unexpected application error', error, info);
|
||||||
|
Sentry.captureException(error, { extra: { componentStack: info.componentStack } });
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): ReactNode {
|
render(): ReactNode {
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
import * as Sentry from '@sentry/react';
|
||||||
|
import { ErrorBoundary } from '../ErrorBoundary';
|
||||||
|
|
||||||
|
vi.mock('@sentry/react', () => ({
|
||||||
|
captureException: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
function ThrowingChild(): never {
|
||||||
|
throw new Error('boom');
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ErrorBoundary', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders children when there is no error', () => {
|
||||||
|
render(
|
||||||
|
<ErrorBoundary>
|
||||||
|
<p>Alles werkt</p>
|
||||||
|
</ErrorBoundary>,
|
||||||
|
);
|
||||||
|
expect(screen.getByText('Alles werkt')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the generic fallback message when a child throws', () => {
|
||||||
|
// Suppress the expected React error boundary console.error noise for this test.
|
||||||
|
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<ErrorBoundary>
|
||||||
|
<ThrowingChild />
|
||||||
|
</ErrorBoundary>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('error-boundary-fallback')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Er ging iets mis. Probeer de pagina te vernieuwen.')).toBeInTheDocument();
|
||||||
|
|
||||||
|
consoleErrorSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reports the caught error to Sentry', () => {
|
||||||
|
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<ErrorBoundary>
|
||||||
|
<ThrowingChild />
|
||||||
|
</ErrorBoundary>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(Sentry.captureException).toHaveBeenCalledTimes(1);
|
||||||
|
expect(Sentry.captureException).toHaveBeenCalledWith(
|
||||||
|
expect.any(Error),
|
||||||
|
expect.objectContaining({ extra: expect.objectContaining({ componentStack: expect.any(String) }) }),
|
||||||
|
);
|
||||||
|
|
||||||
|
consoleErrorSpy.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,10 +1,16 @@
|
|||||||
import { StrictMode } from 'react';
|
import { StrictMode } from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import { RouterProvider } from '@tanstack/react-router';
|
import { RouterProvider } from '@tanstack/react-router';
|
||||||
|
import * as Sentry from '@sentry/react';
|
||||||
import './fonts';
|
import './fonts';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import { router } from './router';
|
import { router } from './router';
|
||||||
|
|
||||||
|
const sentryDsn = import.meta.env.VITE_SENTRY_DSN;
|
||||||
|
if (sentryDsn) {
|
||||||
|
Sentry.init({ dsn: sentryDsn });
|
||||||
|
}
|
||||||
|
|
||||||
const rootElement = document.getElementById('root');
|
const rootElement = document.getElementById('root');
|
||||||
if (!rootElement) {
|
if (!rootElement) {
|
||||||
throw new Error('Root element "#root" not found in index.html');
|
throw new Error('Root element "#root" not found in index.html');
|
||||||
|
|||||||
Vendored
+10
@@ -0,0 +1,10 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
/** Sentry DSN (Data Source Name) for client-side error reporting. Not a secret — safe to expose in the client bundle. */
|
||||||
|
readonly VITE_SENTRY_DSN?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv;
|
||||||
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
{"root":["./src/fonts.ts","./src/main.tsx","./src/queryclient.ts","./src/router.tsx","./src/components/aboutsection.tsx","./src/components/contactsection.tsx","./src/components/errorboundary.tsx","./src/components/footer.tsx","./src/components/hero.tsx","./src/components/nav.tsx","./src/components/packagecard.tsx","./src/components/packagessection.tsx","./src/components/processsection.tsx","./src/components/processstep.tsx","./src/components/rootlayout.tsx","./src/components/themetoggle.tsx","./src/components/__tests__/nav.test.tsx","./src/components/__tests__/packagessection.test.tsx","./src/data/content.ts","./src/hooks/usepackagesquery.ts","./src/routes/__root.tsx","./src/routes/index.tsx","./src/test/setup.ts","./src/theme/themeprovider.tsx","./src/theme/tokens.ts","./src/theme/__tests__/themeprovider.test.tsx","./src/utils/scrolltohash.ts"],"version":"5.9.3"}
|
{"root":["./src/fonts.ts","./src/main.tsx","./src/queryclient.ts","./src/router.tsx","./src/vite-env.d.ts","./src/components/aboutsection.tsx","./src/components/contactsection.tsx","./src/components/errorboundary.tsx","./src/components/footer.tsx","./src/components/hero.tsx","./src/components/nav.tsx","./src/components/packagecard.tsx","./src/components/packagessection.tsx","./src/components/processsection.tsx","./src/components/processstep.tsx","./src/components/rootlayout.tsx","./src/components/themetoggle.tsx","./src/components/__tests__/errorboundary.test.tsx","./src/components/__tests__/nav.test.tsx","./src/components/__tests__/packagessection.test.tsx","./src/data/content.ts","./src/hooks/usepackagesquery.ts","./src/routes/__root.tsx","./src/routes/index.tsx","./src/test/setup.ts","./src/theme/themeprovider.tsx","./src/theme/tokens.ts","./src/theme/__tests__/themeprovider.test.tsx","./src/utils/scrolltohash.ts"],"version":"5.9.3"}
|
||||||
Reference in New Issue
Block a user