feat(web): instrumentation PostHog (analytics + nginx proxy)
Some checks failed
Build & Deploy Web / build-and-deploy (push) Has been cancelled
Build & Deploy API / build-and-deploy (push) Successful in 2m30s
Build & Deploy Landing / build-and-deploy (push) Successful in 1m31s

Setup PostHog côté SPA — pageviews TanStack Router + 10 events business
(signup, login SSO, upload facture, émission/brouillon facture native,
marquer payée, lancer relance, plan créé, checkout Stripe). PostHogProvider
dans __root.tsx, identify sur auth, proxy nginx /ingest/* → eu.i.posthog.com
pour contourner les adblockers. Token bake via build-arg CI
(POSTHOG_PROJECT_TOKEN, à ajouter côté Gitea Secrets).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
ordinarthur 2026-05-15 17:21:59 +02:00
parent e9e0ac6abb
commit 3fc3a7456a
19 changed files with 437 additions and 5 deletions

View File

@ -63,6 +63,8 @@ jobs:
VITE_APP_VERSION=${{ github.sha }} VITE_APP_VERSION=${{ github.sha }}
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG=${{ secrets.SENTRY_ORG }} SENTRY_ORG=${{ secrets.SENTRY_ORG }}
VITE_PUBLIC_POSTHOG_PROJECT_TOKEN=${{ secrets.POSTHOG_PROJECT_TOKEN }}
VITE_PUBLIC_POSTHOG_HOST=https://eu.posthog.com
- name: Install kubectl - name: Install kubectl
run: | run: |

View File

@ -25,6 +25,11 @@ ARG VITE_APP_VERSION=
# sourcemaps (cf. vite.config.ts). Ne fuit pas en runtime (consommé au build). # sourcemaps (cf. vite.config.ts). Ne fuit pas en runtime (consommé au build).
ARG SENTRY_AUTH_TOKEN= ARG SENTRY_AUTH_TOKEN=
ARG SENTRY_ORG=rubis ARG SENTRY_ORG=rubis
# PostHog — token public baked dans le bundle (phc_*). UI host = lien
# « voir dans PostHog » uniquement ; le data ingestion passe par /ingest
# proxifié vers eu.i.posthog.com par nginx (cf. apps/web/nginx.conf).
ARG VITE_PUBLIC_POSTHOG_PROJECT_TOKEN=
ARG VITE_PUBLIC_POSTHOG_HOST=https://eu.posthog.com
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# build — Vite produit dist/ # build — Vite produit dist/
@ -61,6 +66,8 @@ ARG VITE_SENTRY_DSN_WEB
ARG VITE_APP_VERSION ARG VITE_APP_VERSION
ARG SENTRY_AUTH_TOKEN ARG SENTRY_AUTH_TOKEN
ARG SENTRY_ORG ARG SENTRY_ORG
ARG VITE_PUBLIC_POSTHOG_PROJECT_TOKEN
ARG VITE_PUBLIC_POSTHOG_HOST
# vite build direct (le `tsc -b` du script build plante sans cache .tsbuildinfo # vite build direct (le `tsc -b` du script build plante sans cache .tsbuildinfo
# à cause de @tanstack/router-core ; le typecheck strict est en CI séparée). # à cause de @tanstack/router-core ; le typecheck strict est en CI séparée).
@ -74,6 +81,8 @@ RUN VITE_API_URL=$VITE_API_URL \
APP_VERSION=$VITE_APP_VERSION \ APP_VERSION=$VITE_APP_VERSION \
SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN \ SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN \
SENTRY_ORG=$SENTRY_ORG \ SENTRY_ORG=$SENTRY_ORG \
VITE_PUBLIC_POSTHOG_PROJECT_TOKEN=$VITE_PUBLIC_POSTHOG_PROJECT_TOKEN \
VITE_PUBLIC_POSTHOG_HOST=$VITE_PUBLIC_POSTHOG_HOST \
pnpm --filter @rubis/web exec vite build pnpm --filter @rubis/web exec vite build
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------

View File

@ -1,6 +1,9 @@
VITE_API_URL=http://localhost:3333 VITE_API_URL=http://localhost:3333
VITE_PUBLIC_LANDING_URL=http://localhost:8080 VITE_PUBLIC_LANDING_URL=http://localhost:8080
VITE_USE_MOCKS=false VITE_USE_MOCKS=false
# PostHog — coller le token "phc_..." du projet (Project settings > Project API key).
VITE_PUBLIC_POSTHOG_PROJECT_TOKEN=phc_yPtTBc6cXyTS8mQVYbYze5fDoRWHD3Cy5iRdn9AR56x8
VITE_PUBLIC_POSTHOG_HOST=https://eu.posthog.com
# Sentry — DSN PUBLIC (peut être commit, c'est by-design dans @sentry/react) # Sentry — DSN PUBLIC (peut être commit, c'est by-design dans @sentry/react)
VITE_SENTRY_DSN_WEB=https://2e62d7ef1a5aad166d6e0ec9a6e31580@o4511353951354880.ingest.de.sentry.io/4511353959874640 VITE_SENTRY_DSN_WEB=https://2e62d7ef1a5aad166d6e0ec9a6e31580@o4511353951354880.ingest.de.sentry.io/4511353959874640
# VITE_APP_VERSION : injecté par le CI (sha git court) ou via vite.config.ts # VITE_APP_VERSION : injecté par le CI (sha git court) ou via vite.config.ts

View File

@ -6,3 +6,9 @@ VITE_PUBLIC_LANDING_URL=https://rubis.pro
# Active MSW pour mocker l'API. Laisser à "false" pour taper le vrai backend. # Active MSW pour mocker l'API. Laisser à "false" pour taper le vrai backend.
VITE_USE_MOCKS=false VITE_USE_MOCKS=false
# PostHog — récupérer dans Project settings > Project API key (commence par "phc_").
# Le token est PUBLIC (envoyé au navigateur), pas besoin de le cacher.
# UI host : https://eu.posthog.com (EU) ou https://us.posthog.com (US).
VITE_PUBLIC_POSTHOG_PROJECT_TOKEN=
VITE_PUBLIC_POSTHOG_HOST=https://eu.posthog.com

1
apps/web/.gitignore vendored
View File

@ -22,3 +22,4 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
.env

View File

@ -3,6 +3,7 @@
# Sert : # Sert :
# - / assets SPA + index.html avec fallback try_files (TanStack Router) # - / assets SPA + index.html avec fallback try_files (TanStack Router)
# - /api/* reverse proxy vers le service ClusterIP rubis-api:3333 # - /api/* reverse proxy vers le service ClusterIP rubis-api:3333
# - /ingest/* reverse proxy vers PostHog EU (contourne les adblockers)
# #
# Le service rubis-api est interne au cluster K3s (pas de NodePort). # Le service rubis-api est interne au cluster K3s (pas de NodePort).
# Seul nginx (NodePort 30110 Traefik) est exposé. # Seul nginx (NodePort 30110 Traefik) est exposé.
@ -12,6 +13,19 @@ upstream rubis_api {
keepalive 32; keepalive 32;
} }
# PostHog ingestion endpoint (EU) proxifié pour passer les adblockers
# qui bloquent *.posthog.com directement. Le SDK est configuré avec
# api_host: "/ingest" (cf. src/routes/__root.tsx).
upstream posthog_ingest {
server eu.i.posthog.com:443;
keepalive 8;
}
# Assets PostHog (static.js, array.js, recorder) domaine distinct chez PostHog.
upstream posthog_assets {
server eu-assets.i.posthog.com:443;
keepalive 8;
}
# Compression gzip Vite produit déjà du JS minifié, mais HTML/CSS/SVG # Compression gzip Vite produit déjà du JS minifié, mais HTML/CSS/SVG
# bénéficient toujours du gzip on-the-fly. # bénéficient toujours du gzip on-the-fly.
gzip on; gzip on;
@ -63,6 +77,33 @@ server {
proxy_read_timeout 60s; proxy_read_timeout 60s;
} }
# PostHog assets (loader JS) /ingest/static/* et /ingest/array/* vont
# vers eu-assets.i.posthog.com (CDN). Doit être déclaré AVANT /ingest/
# car nginx matche par prefix le plus long.
location ~ ^/ingest/(static|array)/ {
rewrite ^/ingest/(.*)$ /$1 break;
proxy_pass https://posthog_assets;
proxy_http_version 1.1;
proxy_set_header Host eu-assets.i.posthog.com;
proxy_set_header Connection "";
proxy_ssl_server_name on;
proxy_ssl_name eu-assets.i.posthog.com;
}
# PostHog ingestion (events, sessions, exceptions) /ingest/* eu.i.posthog.com
location /ingest/ {
rewrite ^/ingest/(.*)$ /$1 break;
proxy_pass https://posthog_ingest;
proxy_http_version 1.1;
proxy_set_header Host eu.i.posthog.com;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Connection "";
proxy_ssl_server_name on;
proxy_ssl_name eu.i.posthog.com;
proxy_redirect off;
}
# SPA fallback : toute route non-asset, non-API index.html # SPA fallback : toute route non-asset, non-API index.html
# (TanStack Router gère côté client). # (TanStack Router gère côté client).
location / { location / {

View File

@ -28,6 +28,7 @@
"@radix-ui/react-tooltip": "^1.1.8", "@radix-ui/react-tooltip": "^1.1.8",
"@rubis/shared": "workspace:*", "@rubis/shared": "workspace:*",
"@rubis/ui": "workspace:*", "@rubis/ui": "workspace:*",
"@posthog/react": "^1.9.0",
"@sentry/react": "^10.52.0", "@sentry/react": "^10.52.0",
"@tanstack/react-form": "^1.0.0", "@tanstack/react-form": "^1.0.0",
"@tanstack/react-query": "^5.66.0", "@tanstack/react-query": "^5.66.0",
@ -43,6 +44,7 @@
"react": "^19.2.5", "react": "^19.2.5",
"react-dom": "^19.2.5", "react-dom": "^19.2.5",
"recharts": "^3.8.1", "recharts": "^3.8.1",
"posthog-js": "^1.250.0",
"sonner": "^1.7.4", "sonner": "^1.7.4",
"tailwind-merge": "^3.0.1", "tailwind-merge": "^3.0.1",
"zod": "^3.24.1" "zod": "^3.24.1"

View File

@ -1,7 +1,8 @@
import { Outlet, createRootRouteWithContext } from "@tanstack/react-router"; import { Outlet, createRootRouteWithContext, useRouterState } from "@tanstack/react-router";
import type { QueryClient } from "@tanstack/react-query"; import type { QueryClient } from "@tanstack/react-query";
import { Toaster } from "sonner"; import { Toaster } from "sonner";
import { lazy, Suspense } from "react"; import { lazy, Suspense, useEffect } from "react";
import { PostHogProvider, usePostHog } from "@posthog/react";
import { VersionToast } from "../components/version-toast"; import { VersionToast } from "../components/version-toast";
@ -34,9 +35,32 @@ export const Route = createRootRouteWithContext<RouterContext>()({
notFoundComponent: NotFound, notFoundComponent: NotFound,
}); });
function PostHogPageview() {
const posthog = usePostHog();
const location = useRouterState({ select: (s) => s.location });
useEffect(() => {
posthog.capture("$pageview", { $current_url: window.location.href });
}, [location.pathname, location.search, posthog]);
return null;
}
function RootLayout() { function RootLayout() {
return ( return (
<> <PostHogProvider
apiKey={import.meta.env.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN!}
options={{
api_host: "/ingest",
ui_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST || "https://eu.posthog.com",
defaults: "2026-01-30",
capture_exceptions: true,
autocapture: false,
capture_pageview: false,
debug: import.meta.env.DEV,
}}
>
<PostHogPageview />
<Outlet /> <Outlet />
<Toaster <Toaster
position="bottom-right" position="bottom-right"
@ -56,7 +80,7 @@ function RootLayout() {
<ReactQueryDevtools buttonPosition="bottom-right" /> <ReactQueryDevtools buttonPosition="bottom-right" />
</Suspense> </Suspense>
)} )}
</> </PostHogProvider>
); );
} }

View File

@ -4,6 +4,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { ArrowLeft, Check, Send } from "lucide-react"; import { ArrowLeft, Check, Send } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import { usePostHog } from "@posthog/react";
import type { Client, Invoice, Plan } from "@rubis/shared"; import type { Client, Invoice, Plan } from "@rubis/shared";
import { api } from "@/lib/api"; import { api } from "@/lib/api";
@ -48,6 +49,7 @@ function InvoiceDetailPage() {
const search = Route.useSearch(); const search = Route.useSearch();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const navigate = useNavigate(); const navigate = useNavigate();
const posthog = usePostHog();
const { const {
data: invoice, data: invoice,
@ -61,6 +63,7 @@ function InvoiceDetailPage() {
const markPaidMutation = useMutation({ const markPaidMutation = useMutation({
mutationFn: () => api.post<InvoiceDetail>(`/api/v1/invoices/${id}/mark-paid`), mutationFn: () => api.post<InvoiceDetail>(`/api/v1/invoices/${id}/mark-paid`),
onSuccess: () => { onSuccess: () => {
posthog.capture("invoice_marked_paid", { invoice_id: id });
void queryClient.invalidateQueries({ queryKey: queryKeys.invoices.all() }); void queryClient.invalidateQueries({ queryKey: queryKeys.invoices.all() });
void queryClient.invalidateQueries({ queryKey: queryKeys.dashboard.kpis() }); void queryClient.invalidateQueries({ queryKey: queryKeys.dashboard.kpis() });
toast.success("Encaissée. + 1 rubis bien mérité."); toast.success("Encaissée. + 1 rubis bien mérité.");
@ -184,6 +187,7 @@ function InvoiceDetailPage() {
onClick={() => onClick={() =>
launchRelanceMutation.mutate(invoice.id, { launchRelanceMutation.mutate(invoice.id, {
onSuccess: () => { onSuccess: () => {
posthog.capture("invoice_relance_launched", { invoice_id: invoice.id });
toast.success( toast.success(
`Relances activées pour ${invoice.numero}.`, `Relances activées pour ${invoice.numero}.`,
); );

View File

@ -2,6 +2,7 @@ import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import { ArrowLeft, FilePlus } from "lucide-react"; import { ArrowLeft, FilePlus } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
import { usePostHog } from "@posthog/react";
import { Button } from "@rubis/ui"; import { Button } from "@rubis/ui";
import { Eyebrow } from "@rubis/ui"; import { Eyebrow } from "@rubis/ui";
@ -16,10 +17,12 @@ export const Route = createFileRoute("/_app/factures_/import")({
function ImportLandingPage() { function ImportLandingPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const manual = useManualInvoice(); const manual = useManualInvoice();
const posthog = usePostHog();
const upload = useMutation({ const upload = useMutation({
mutationFn: uploadInvoiceFiles, mutationFn: uploadInvoiceFiles,
onSuccess: (batch) => { onSuccess: (batch) => {
posthog.capture("invoice_uploaded", { count: batch.drafts.length });
toast.success( toast.success(
`${batch.drafts.length} facture${batch.drafts.length > 1 ? "s" : ""} extraite${ `${batch.drafts.length} facture${batch.drafts.length > 1 ? "s" : ""} extraite${
batch.drafts.length > 1 ? "s" : "" batch.drafts.length > 1 ? "s" : ""

View File

@ -1,6 +1,7 @@
import { useEffect, useMemo, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router"; import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { usePostHog } from "@posthog/react";
import { addDays } from "date-fns"; import { addDays } from "date-fns";
import { import {
ArrowLeft, ArrowLeft,
@ -45,6 +46,7 @@ const newLine = (): InvoiceLineInput => ({
function FacturesNouvellePage() { function FacturesNouvellePage() {
const navigate = useNavigate(); const navigate = useNavigate();
const posthog = usePostHog();
const { data: settings } = useInvoiceSettings(); const { data: settings } = useInvoiceSettings();
const { data: themes } = useInvoiceThemes(); const { data: themes } = useInvoiceThemes();
const { data: plans } = useQuery({ const { data: plans } = useQuery({
@ -121,6 +123,11 @@ function FacturesNouvellePage() {
}; };
try { try {
const invoice = await create.mutateAsync(payload); const invoice = await create.mutateAsync(payload);
posthog.capture(draft ? "invoice_draft_saved" : "invoice_emitted", {
plan_id: planId ?? undefined,
theme_slug: themeSlug,
line_count: lines.length,
});
navigate({ to: "/factures/$id", params: { id: invoice.id } }); navigate({ to: "/factures/$id", params: { id: invoice.id } });
} catch (err) { } catch (err) {
setSubmitError(err instanceof Error ? err.message : "Erreur inconnue"); setSubmitError(err instanceof Error ? err.message : "Erreur inconnue");

View File

@ -3,6 +3,7 @@ import { createFileRoute, Link } from "@tanstack/react-router";
import { ArrowLeft, ArrowRight, CreditCard, RotateCcw, Clock } from "lucide-react"; import { ArrowLeft, ArrowRight, CreditCard, RotateCcw, Clock } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import { usePostHog } from "@posthog/react";
import { import {
useOpenPortal, useOpenPortal,
@ -35,6 +36,7 @@ function AbonnementPage() {
const checkout = useStartCheckout(); const checkout = useStartCheckout();
const portal = useOpenPortal(); const portal = useOpenPortal();
const reactivate = useReactivateSubscription(); const reactivate = useReactivateSubscription();
const posthog = usePostHog();
const [cycle, setCycle] = useState<BillingCycle>("monthly"); const [cycle, setCycle] = useState<BillingCycle>("monthly");
useEffect(() => { useEffect(() => {
@ -46,6 +48,7 @@ function AbonnementPage() {
}, [search.checkout]); }, [search.checkout]);
const onUpgrade = (plan: "pro" | "business") => { const onUpgrade = (plan: "pro" | "business") => {
posthog.capture("checkout_started", { plan, cycle });
checkout.mutate( checkout.mutate(
{ plan, cycle }, { plan, cycle },
{ {

View File

@ -1,6 +1,7 @@
import { useEffect, useMemo, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import { createFileRoute, useNavigate, Link } from "@tanstack/react-router"; import { createFileRoute, useNavigate, Link } from "@tanstack/react-router";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { usePostHog } from "@posthog/react";
import { z } from "zod"; import { z } from "zod";
import { import {
ArrowLeft, ArrowLeft,
@ -125,6 +126,7 @@ function PlanCreateWizard() {
const navigate = useNavigate(); const navigate = useNavigate();
const { from } = Route.useSearch(); const { from } = Route.useSearch();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const posthog = usePostHog();
const [step, setStep] = useState<WizardStep>(1); const [step, setStep] = useState<WizardStep>(1);
// Si on duplique, on charge le plan source pour pré-remplir le draft. // Si on duplique, on charge le plan source pour pré-remplir le draft.
@ -184,6 +186,11 @@ function PlanCreateWizard() {
})), })),
}), }),
onSuccess: (created) => { onSuccess: (created) => {
posthog.capture("plan_created", {
plan_name: created.name,
step_count: draft.steps.length,
global_tone: draft.globalTone,
});
void queryClient.invalidateQueries({ queryKey: queryKeys.plans.all() }); void queryClient.invalidateQueries({ queryKey: queryKeys.plans.all() });
toast.success(`Plan « ${created.name} » créé.`); toast.success(`Plan « ${created.name} » créé.`);
void navigate({ void navigate({

View File

@ -2,6 +2,7 @@ import { useEffect, useRef } from "react";
import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { z } from "zod"; import { z } from "zod";
import { toast } from "sonner"; import { toast } from "sonner";
import { usePostHog } from "@posthog/react";
import type { AuthSession } from "@rubis/shared"; import type { AuthSession } from "@rubis/shared";
import { api } from "@/lib/api"; import { api } from "@/lib/api";
@ -31,6 +32,7 @@ export const Route = createFileRoute("/auth/sso/complete")({
function SsoCompletePage() { function SsoCompletePage() {
const { next } = Route.useSearch(); const { next } = Route.useSearch();
const navigate = useNavigate(); const navigate = useNavigate();
const posthog = usePostHog();
// Strict-mode protect : avoid double-firing the refresh in dev. // Strict-mode protect : avoid double-firing the refresh in dev.
const triggered = useRef(false); const triggered = useRef(false);
@ -44,6 +46,11 @@ function SsoCompletePage() {
undefined, undefined,
{ anonymous: true }, { anonymous: true },
); );
posthog.identify(session.user.id, {
email: session.user.email,
name: session.user.fullName,
});
posthog.capture("user_logged_in_sso");
authStore.setSession(session.accessToken, session.user); authStore.setSession(session.accessToken, session.user);
const firstName = session.user.fullName?.split(" ")[0]; const firstName = session.user.fullName?.split(" ")[0];
toast.success(firstName ? `Bonjour ${firstName}.` : "Connecté."); toast.success(firstName ? `Bonjour ${firstName}.` : "Connecté.");

View File

@ -5,6 +5,7 @@ import { useMutation } from "@tanstack/react-query";
import { toast } from "sonner"; import { toast } from "sonner";
import { ArrowRight } from "lucide-react"; import { ArrowRight } from "lucide-react";
import { z } from "zod"; import { z } from "zod";
import { usePostHog } from "@posthog/react";
import { loginSchema, type AuthSession, type LoginInput } from "@rubis/shared"; import { loginSchema, type AuthSession, type LoginInput } from "@rubis/shared";
@ -51,6 +52,7 @@ export const Route = createFileRoute("/login")({
function LoginPage() { function LoginPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const search = Route.useSearch(); const search = Route.useSearch();
const posthog = usePostHog();
// Toast d'erreur si on revient d'un échec SSO (?google=denied, ?microsoft=…). // Toast d'erreur si on revient d'un échec SSO (?google=denied, ?microsoft=…).
useEffect(() => { useEffect(() => {
@ -66,6 +68,11 @@ function LoginPage() {
mutationFn: async (input: LoginInput) => mutationFn: async (input: LoginInput) =>
api.post<AuthSession>("/api/v1/auth/login", input, { anonymous: true }), api.post<AuthSession>("/api/v1/auth/login", input, { anonymous: true }),
onSuccess: (session) => { onSuccess: (session) => {
posthog.identify(session.user.id, {
email: session.user.email,
name: session.user.fullName,
});
posthog.capture("user_logged_in", { provider: "email" });
authStore.setSession(session.accessToken, session.user); authStore.setSession(session.accessToken, session.user);
toast.success(`Bonjour ${session.user.fullName.split(" ")[0]}.`); toast.success(`Bonjour ${session.user.fullName.split(" ")[0]}.`);
// Si on a une URL de redirection (depuis le guard d'auth), on la suit ; // Si on a une URL de redirection (depuis le guard d'auth), on la suit ;

View File

@ -3,6 +3,7 @@ import { useForm } from "@tanstack/react-form";
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import { toast } from "sonner"; import { toast } from "sonner";
import { ArrowRight } from "lucide-react"; import { ArrowRight } from "lucide-react";
import { usePostHog } from "@posthog/react";
import { import {
registerSchema, registerSchema,
@ -27,11 +28,17 @@ export const Route = createFileRoute("/signup")({
function SignupPage() { function SignupPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const posthog = usePostHog();
const signupMutation = useMutation({ const signupMutation = useMutation({
mutationFn: async (input: RegisterInput) => mutationFn: async (input: RegisterInput) =>
api.post<AuthSession>("/api/v1/auth/signup", input, { anonymous: true }), api.post<AuthSession>("/api/v1/auth/signup", input, { anonymous: true }),
onSuccess: (session) => { onSuccess: (session) => {
posthog.identify(session.user.id, {
email: session.user.email,
name: session.user.fullName,
});
posthog.capture("user_signed_up", { email: session.user.email });
authStore.setSession(session.accessToken, session.user); authStore.setSession(session.accessToken, session.user);
toast.success("Compte créé. On finalise votre installation."); toast.success("Compte créé. On finalise votre installation.");
void navigate({ to: "/onboarding/compte" }); void navigate({ to: "/onboarding/compte" });

View File

@ -68,6 +68,23 @@ export default defineConfig({
server: { server: {
port: 5173, port: 5173,
strictPort: true, strictPort: true,
proxy: {
"/ingest/static": {
target: "https://eu-assets.i.posthog.com",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/ingest/, ""),
},
"/ingest/array": {
target: "https://eu-assets.i.posthog.com",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/ingest/, ""),
},
"/ingest": {
target: "https://eu.i.posthog.com",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/ingest/, ""),
},
},
}, },
build: { build: {
// Source maps requises pour que Sentry désobfusque les stack traces. // Source maps requises pour que Sentry désobfusque les stack traces.

View File

@ -57,7 +57,7 @@ export function Brand({
<Gem size={resolvedSize} /> <Gem size={resolvedSize} />
<span className="leading-none"> <span className="leading-none">
Rubis Rubis
{withSuffix && <span className="text-ink-3">.pro</span>} {/* {withSuffix && <span className="text-ink-3 ml-1 text-xs pb-5">sur l'ongle</span>} */}
</span> </span>
</span> </span>
); );

282
pnpm-lock.yaml generated
View File

@ -244,6 +244,9 @@ importers:
'@fontsource-variable/inter': '@fontsource-variable/inter':
specifier: ^5.2.5 specifier: ^5.2.5
version: 5.2.8 version: 5.2.8
'@posthog/react':
specifier: ^1.9.0
version: 1.9.0(@types/react@19.2.14)(posthog-js@1.373.5)(react@19.2.5)
'@radix-ui/react-dialog': '@radix-ui/react-dialog':
specifier: ^1.1.6 specifier: ^1.1.6
version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
@ -304,6 +307,9 @@ importers:
lucide-react: lucide-react:
specifier: ^0.475.0 specifier: ^0.475.0
version: 0.475.0(react@19.2.5) version: 0.475.0(react@19.2.5)
posthog-js:
specifier: ^1.250.0
version: 1.373.5
react: react:
specifier: ^19.2.5 specifier: ^19.2.5
version: 19.2.5 version: 19.2.5
@ -1898,6 +1904,10 @@ packages:
resolution: {integrity: sha512-lAb0jQRVyleQQGiuuvCOTDVspc14nx6XJjP4FspJ1sNARo3Regq4ZZbrc3rN4b1TYSuUCvgH+UXUPug4SLOqEQ==} resolution: {integrity: sha512-lAb0jQRVyleQQGiuuvCOTDVspc14nx6XJjP4FspJ1sNARo3Regq4ZZbrc3rN4b1TYSuUCvgH+UXUPug4SLOqEQ==}
engines: {node: '>=8.0.0'} engines: {node: '>=8.0.0'}
'@opentelemetry/api-logs@0.208.0':
resolution: {integrity: sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==}
engines: {node: '>=8.0.0'}
'@opentelemetry/api-logs@0.212.0': '@opentelemetry/api-logs@0.212.0':
resolution: {integrity: sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==} resolution: {integrity: sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==}
engines: {node: '>=8.0.0'} engines: {node: '>=8.0.0'}
@ -1910,6 +1920,12 @@ packages:
resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==}
engines: {node: '>=8.0.0'} engines: {node: '>=8.0.0'}
'@opentelemetry/core@2.2.0':
resolution: {integrity: sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==}
engines: {node: ^18.19.0 || >=20.6.0}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
'@opentelemetry/core@2.6.1': '@opentelemetry/core@2.6.1':
resolution: {integrity: sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==} resolution: {integrity: sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==}
engines: {node: ^18.19.0 || >=20.6.0} engines: {node: ^18.19.0 || >=20.6.0}
@ -1922,6 +1938,12 @@ packages:
peerDependencies: peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0' '@opentelemetry/api': '>=1.0.0 <1.10.0'
'@opentelemetry/exporter-logs-otlp-http@0.208.0':
resolution: {integrity: sha512-jOv40Bs9jy9bZVLo/i8FwUiuCvbjWDI+ZW13wimJm4LjnlwJxGgB+N/VWOZUTpM+ah/awXeQqKdNlpLf2EjvYg==}
engines: {node: ^18.19.0 || >=20.6.0}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/instrumentation-amqplib@0.61.0': '@opentelemetry/instrumentation-amqplib@0.61.0':
resolution: {integrity: sha512-mCKoyTGfRNisge4br0NpOFSy2Z1NnEW8hbCJdUDdJFHrPqVzc4IIBPA/vX0U+LUcQqrQvJX+HMIU0dbDRe0i0Q==} resolution: {integrity: sha512-mCKoyTGfRNisge4br0NpOFSy2Z1NnEW8hbCJdUDdJFHrPqVzc4IIBPA/vX0U+LUcQqrQvJX+HMIU0dbDRe0i0Q==}
engines: {node: ^18.19.0 || >=20.6.0} engines: {node: ^18.19.0 || >=20.6.0}
@ -2048,12 +2070,48 @@ packages:
peerDependencies: peerDependencies:
'@opentelemetry/api': ^1.3.0 '@opentelemetry/api': ^1.3.0
'@opentelemetry/otlp-exporter-base@0.208.0':
resolution: {integrity: sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA==}
engines: {node: ^18.19.0 || >=20.6.0}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/otlp-transformer@0.208.0':
resolution: {integrity: sha512-DCFPY8C6lAQHUNkzcNT9R+qYExvsk6C5Bto2pbNxgicpcSWbe2WHShLxkOxIdNcBiYPdVHv/e7vH7K6TI+C+fQ==}
engines: {node: ^18.19.0 || >=20.6.0}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/resources@2.2.0':
resolution: {integrity: sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==}
engines: {node: ^18.19.0 || >=20.6.0}
peerDependencies:
'@opentelemetry/api': '>=1.3.0 <1.10.0'
'@opentelemetry/resources@2.7.1': '@opentelemetry/resources@2.7.1':
resolution: {integrity: sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==} resolution: {integrity: sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==}
engines: {node: ^18.19.0 || >=20.6.0} engines: {node: ^18.19.0 || >=20.6.0}
peerDependencies: peerDependencies:
'@opentelemetry/api': '>=1.3.0 <1.10.0' '@opentelemetry/api': '>=1.3.0 <1.10.0'
'@opentelemetry/sdk-logs@0.208.0':
resolution: {integrity: sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA==}
engines: {node: ^18.19.0 || >=20.6.0}
peerDependencies:
'@opentelemetry/api': '>=1.4.0 <1.10.0'
'@opentelemetry/sdk-metrics@2.2.0':
resolution: {integrity: sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==}
engines: {node: ^18.19.0 || >=20.6.0}
peerDependencies:
'@opentelemetry/api': '>=1.9.0 <1.10.0'
'@opentelemetry/sdk-trace-base@2.2.0':
resolution: {integrity: sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==}
engines: {node: ^18.19.0 || >=20.6.0}
peerDependencies:
'@opentelemetry/api': '>=1.3.0 <1.10.0'
'@opentelemetry/sdk-trace-base@2.7.1': '@opentelemetry/sdk-trace-base@2.7.1':
resolution: {integrity: sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==} resolution: {integrity: sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==}
engines: {node: ^18.19.0 || >=20.6.0} engines: {node: ^18.19.0 || >=20.6.0}
@ -2148,11 +2206,57 @@ packages:
'@poppinss/validator-lite@2.1.2': '@poppinss/validator-lite@2.1.2':
resolution: {integrity: sha512-UhSG1ouT6r67VbEFHK/8ax3EMZYHioew9PqGmEZjV41G15aPZi6cyhXtBVvF9xqkHMflA5V680k7bQzV0kfD5w==} resolution: {integrity: sha512-UhSG1ouT6r67VbEFHK/8ax3EMZYHioew9PqGmEZjV41G15aPZi6cyhXtBVvF9xqkHMflA5V680k7bQzV0kfD5w==}
'@posthog/core@1.29.2':
resolution: {integrity: sha512-DYhR0Sl7pVdUXa+C9poCVjTj3D6SI9P7RLhIhr74YyHeHuCGL/MZsDEWcz3ul3qHDIhZU9myIUjID890QiQw+g==}
'@posthog/react@1.9.0':
resolution: {integrity: sha512-lVdTsWT5+PtHBu44gSQ7QohbLjAYqHkFAIGAQ+HV8Eh9yj+OcnQ7mXCmyhaMlTBD3z7D0H1eWMp4vQaFnsIyWQ==}
peerDependencies:
'@types/react': '>=16.8.0'
posthog-js: '>=1.257.2'
react: '>=16.8.0'
peerDependenciesMeta:
'@types/react':
optional: true
'@posthog/types@1.373.5':
resolution: {integrity: sha512-K7STCnRG/WBE1q0BwEkIcrJB5OqECaymsQj6Hp4Ntvaek4dqHkZGfp6hxwIPqQPjlOXwidwPLo+XGsn+CoZUyw==}
'@prisma/instrumentation@7.6.0': '@prisma/instrumentation@7.6.0':
resolution: {integrity: sha512-ZPW2gRiwpPzEfgeZgaekhqXrbW+Y2RJKHVqUmlhZhKzRNCcvR6DykzylDrynpArKKRQtLxoZy36fK7U0p3pdgQ==} resolution: {integrity: sha512-ZPW2gRiwpPzEfgeZgaekhqXrbW+Y2RJKHVqUmlhZhKzRNCcvR6DykzylDrynpArKKRQtLxoZy36fK7U0p3pdgQ==}
peerDependencies: peerDependencies:
'@opentelemetry/api': ^1.8 '@opentelemetry/api': ^1.8
'@protobufjs/aspromise@1.1.2':
resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==}
'@protobufjs/base64@1.1.2':
resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==}
'@protobufjs/codegen@2.0.5':
resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==}
'@protobufjs/eventemitter@1.1.0':
resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==}
'@protobufjs/fetch@1.1.0':
resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==}
'@protobufjs/float@1.0.2':
resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==}
'@protobufjs/inquire@1.1.1':
resolution: {integrity: sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==}
'@protobufjs/path@1.1.2':
resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==}
'@protobufjs/pool@1.1.0':
resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==}
'@protobufjs/utf8@1.1.1':
resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==}
'@radix-ui/number@1.1.1': '@radix-ui/number@1.1.1':
resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
@ -3998,6 +4102,9 @@ packages:
'@types/tedious@4.0.14': '@types/tedious@4.0.14':
resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==} resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==}
'@types/trusted-types@2.0.7':
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
'@types/unist@2.0.11': '@types/unist@2.0.11':
resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
@ -4614,6 +4721,9 @@ packages:
core-js-compat@3.49.0: core-js-compat@3.49.0:
resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==}
core-js@3.49.0:
resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==}
cron-parser@4.9.0: cron-parser@4.9.0:
resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
@ -4856,6 +4966,9 @@ packages:
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
dompurify@3.4.3:
resolution: {integrity: sha512-VVwJidIJcp1hpg2OMXML3ZVRPYSZiq4aX7qBh83BSIpOaRDqI+qxhXjjIWnpzkOXhmp0L81lnoME1mnCc9H48A==}
domutils@3.2.2: domutils@3.2.2:
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
@ -5176,6 +5289,9 @@ packages:
picomatch: picomatch:
optional: true optional: true
fflate@0.4.8:
resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==}
fflate@0.8.2: fflate@0.8.2:
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
@ -6020,6 +6136,9 @@ packages:
resolution: {integrity: sha512-iLs7dGSyjZiUgvrUvuD3FndAxVJk+TywBkkkwUSm9HdYoskJalWg5qVsEiXeufPvRVPbCUmNQewg798rx+sPXg==} resolution: {integrity: sha512-iLs7dGSyjZiUgvrUvuD3FndAxVJk+TywBkkkwUSm9HdYoskJalWg5qVsEiXeufPvRVPbCUmNQewg798rx+sPXg==}
engines: {node: '>=20'} engines: {node: '>=20'}
long@5.3.2:
resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
longest-streak@3.1.0: longest-streak@3.1.0:
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
@ -6668,10 +6787,16 @@ packages:
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
posthog-js@1.373.5:
resolution: {integrity: sha512-VjeSKiAtbRxcKXr+lFWlHNd9GGxA3A1gZ87EsIZmEV3N8SwO11uAf6JDTEuymdUNGn99XTvWcPrBCxkSBgVAEg==}
powershell-utils@0.1.0: powershell-utils@0.1.0:
resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==}
engines: {node: '>=20'} engines: {node: '>=20'}
preact@10.29.1:
resolution: {integrity: sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==}
prebuild-install@7.1.3: prebuild-install@7.1.3:
resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -6727,6 +6852,10 @@ packages:
property-information@7.1.0: property-information@7.1.0:
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
protobufjs@7.5.8:
resolution: {integrity: sha512-dvpCIeLPbXZS/Ete7yLaO7RenOdken2NHKykBXbsaGxZT0UTltcarBciw+A78SRQs9iMAAVpsYA+l8b1hTePIA==}
engines: {node: '>=12.0.0'}
proxy-addr@2.0.7: proxy-addr@2.0.7:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'} engines: {node: '>= 0.10'}
@ -6745,6 +6874,9 @@ packages:
resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==}
engines: {node: '>=0.6'} engines: {node: '>=0.6'}
query-selector-shadow-dom@1.0.1:
resolution: {integrity: sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==}
queue-microtask@1.2.3: queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
@ -8030,6 +8162,9 @@ packages:
web-namespaces@2.0.1: web-namespaces@2.0.1:
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
web-vitals@5.2.0:
resolution: {integrity: sha512-i2z98bEmaCqSDiHEDu+gHl/dmR4Q+TxFmG3/13KkMO+o8UxQzCqWaDRCiLgEa41nlO4VpXSI0ASa1xWmO9sBlA==}
webidl-conversions@3.0.1: webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
@ -9895,6 +10030,10 @@ snapshots:
dependencies: dependencies:
'@opentelemetry/api': 1.9.1 '@opentelemetry/api': 1.9.1
'@opentelemetry/api-logs@0.208.0':
dependencies:
'@opentelemetry/api': 1.9.1
'@opentelemetry/api-logs@0.212.0': '@opentelemetry/api-logs@0.212.0':
dependencies: dependencies:
'@opentelemetry/api': 1.9.1 '@opentelemetry/api': 1.9.1
@ -9905,6 +10044,11 @@ snapshots:
'@opentelemetry/api@1.9.1': {} '@opentelemetry/api@1.9.1': {}
'@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.1)':
dependencies:
'@opentelemetry/api': 1.9.1
'@opentelemetry/semantic-conventions': 1.40.0
'@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.1)': '@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.1)':
dependencies: dependencies:
'@opentelemetry/api': 1.9.1 '@opentelemetry/api': 1.9.1
@ -9915,6 +10059,15 @@ snapshots:
'@opentelemetry/api': 1.9.1 '@opentelemetry/api': 1.9.1
'@opentelemetry/semantic-conventions': 1.40.0 '@opentelemetry/semantic-conventions': 1.40.0
'@opentelemetry/exporter-logs-otlp-http@0.208.0(@opentelemetry/api@1.9.1)':
dependencies:
'@opentelemetry/api': 1.9.1
'@opentelemetry/api-logs': 0.208.0
'@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/otlp-exporter-base': 0.208.0(@opentelemetry/api@1.9.1)
'@opentelemetry/otlp-transformer': 0.208.0(@opentelemetry/api@1.9.1)
'@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.1)
'@opentelemetry/instrumentation-amqplib@0.61.0(@opentelemetry/api@1.9.1)': '@opentelemetry/instrumentation-amqplib@0.61.0(@opentelemetry/api@1.9.1)':
dependencies: dependencies:
'@opentelemetry/api': 1.9.1 '@opentelemetry/api': 1.9.1
@ -10097,12 +10250,55 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@opentelemetry/otlp-exporter-base@0.208.0(@opentelemetry/api@1.9.1)':
dependencies:
'@opentelemetry/api': 1.9.1
'@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/otlp-transformer': 0.208.0(@opentelemetry/api@1.9.1)
'@opentelemetry/otlp-transformer@0.208.0(@opentelemetry/api@1.9.1)':
dependencies:
'@opentelemetry/api': 1.9.1
'@opentelemetry/api-logs': 0.208.0
'@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.1)
'@opentelemetry/sdk-metrics': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.1)
protobufjs: 7.5.8
'@opentelemetry/resources@2.2.0(@opentelemetry/api@1.9.1)':
dependencies:
'@opentelemetry/api': 1.9.1
'@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/semantic-conventions': 1.40.0
'@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.1)': '@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.1)':
dependencies: dependencies:
'@opentelemetry/api': 1.9.1 '@opentelemetry/api': 1.9.1
'@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1)
'@opentelemetry/semantic-conventions': 1.40.0 '@opentelemetry/semantic-conventions': 1.40.0
'@opentelemetry/sdk-logs@0.208.0(@opentelemetry/api@1.9.1)':
dependencies:
'@opentelemetry/api': 1.9.1
'@opentelemetry/api-logs': 0.208.0
'@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/sdk-metrics@2.2.0(@opentelemetry/api@1.9.1)':
dependencies:
'@opentelemetry/api': 1.9.1
'@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.1)':
dependencies:
'@opentelemetry/api': 1.9.1
'@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.1)
'@opentelemetry/semantic-conventions': 1.40.0
'@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1)': '@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1)':
dependencies: dependencies:
'@opentelemetry/api': 1.9.1 '@opentelemetry/api': 1.9.1
@ -10211,6 +10407,19 @@ snapshots:
'@poppinss/validator-lite@2.1.2': {} '@poppinss/validator-lite@2.1.2': {}
'@posthog/core@1.29.2':
dependencies:
'@posthog/types': 1.373.5
'@posthog/react@1.9.0(@types/react@19.2.14)(posthog-js@1.373.5)(react@19.2.5)':
dependencies:
posthog-js: 1.373.5
react: 19.2.5
optionalDependencies:
'@types/react': 19.2.14
'@posthog/types@1.373.5': {}
'@prisma/instrumentation@7.6.0(@opentelemetry/api@1.9.1)': '@prisma/instrumentation@7.6.0(@opentelemetry/api@1.9.1)':
dependencies: dependencies:
'@opentelemetry/api': 1.9.1 '@opentelemetry/api': 1.9.1
@ -10218,6 +10427,29 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@protobufjs/aspromise@1.1.2': {}
'@protobufjs/base64@1.1.2': {}
'@protobufjs/codegen@2.0.5': {}
'@protobufjs/eventemitter@1.1.0': {}
'@protobufjs/fetch@1.1.0':
dependencies:
'@protobufjs/aspromise': 1.1.2
'@protobufjs/inquire': 1.1.1
'@protobufjs/float@1.0.2': {}
'@protobufjs/inquire@1.1.1': {}
'@protobufjs/path@1.1.2': {}
'@protobufjs/pool@1.1.0': {}
'@protobufjs/utf8@1.1.1': {}
'@radix-ui/number@1.1.1': {} '@radix-ui/number@1.1.1': {}
'@radix-ui/primitive@1.1.3': {} '@radix-ui/primitive@1.1.3': {}
@ -12079,6 +12311,9 @@ snapshots:
dependencies: dependencies:
'@types/node': 25.6.0 '@types/node': 25.6.0
'@types/trusted-types@2.0.7':
optional: true
'@types/unist@2.0.11': {} '@types/unist@2.0.11': {}
'@types/unist@3.0.3': {} '@types/unist@3.0.3': {}
@ -12821,6 +13056,8 @@ snapshots:
dependencies: dependencies:
browserslist: 4.28.2 browserslist: 4.28.2
core-js@3.49.0: {}
cron-parser@4.9.0: cron-parser@4.9.0:
dependencies: dependencies:
luxon: 3.7.2 luxon: 3.7.2
@ -13017,6 +13254,10 @@ snapshots:
dependencies: dependencies:
domelementtype: 2.3.0 domelementtype: 2.3.0
dompurify@3.4.3:
optionalDependencies:
'@types/trusted-types': 2.0.7
domutils@3.2.2: domutils@3.2.2:
dependencies: dependencies:
dom-serializer: 2.0.0 dom-serializer: 2.0.0
@ -13408,6 +13649,8 @@ snapshots:
optionalDependencies: optionalDependencies:
picomatch: 4.0.4 picomatch: 4.0.4
fflate@0.4.8: {}
fflate@0.8.2: {} fflate@0.8.2: {}
figures@6.1.0: figures@6.1.0:
@ -14265,6 +14508,8 @@ snapshots:
strip-ansi: 7.2.0 strip-ansi: 7.2.0
wrap-ansi: 10.0.0 wrap-ansi: 10.0.0
long@5.3.2: {}
longest-streak@3.1.0: {} longest-streak@3.1.0: {}
loose-envify@1.4.0: loose-envify@1.4.0:
@ -15101,8 +15346,26 @@ snapshots:
dependencies: dependencies:
xtend: 4.0.2 xtend: 4.0.2
posthog-js@1.373.5:
dependencies:
'@opentelemetry/api': 1.9.1
'@opentelemetry/api-logs': 0.208.0
'@opentelemetry/exporter-logs-otlp-http': 0.208.0(@opentelemetry/api@1.9.1)
'@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1)
'@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.1)
'@posthog/core': 1.29.2
'@posthog/types': 1.373.5
core-js: 3.49.0
dompurify: 3.4.3
fflate: 0.4.8
preact: 10.29.1
query-selector-shadow-dom: 1.0.1
web-vitals: 5.2.0
powershell-utils@0.1.0: {} powershell-utils@0.1.0: {}
preact@10.29.1: {}
prebuild-install@7.1.3: prebuild-install@7.1.3:
dependencies: dependencies:
detect-libc: 2.1.2 detect-libc: 2.1.2
@ -15165,6 +15428,21 @@ snapshots:
property-information@7.1.0: {} property-information@7.1.0: {}
protobufjs@7.5.8:
dependencies:
'@protobufjs/aspromise': 1.1.2
'@protobufjs/base64': 1.1.2
'@protobufjs/codegen': 2.0.5
'@protobufjs/eventemitter': 1.1.0
'@protobufjs/fetch': 1.1.0
'@protobufjs/float': 1.0.2
'@protobufjs/inquire': 1.1.1
'@protobufjs/path': 1.1.2
'@protobufjs/pool': 1.1.0
'@protobufjs/utf8': 1.1.1
'@types/node': 25.6.0
long: 5.3.2
proxy-addr@2.0.7: proxy-addr@2.0.7:
dependencies: dependencies:
forwarded: 0.2.0 forwarded: 0.2.0
@ -15183,6 +15461,8 @@ snapshots:
dependencies: dependencies:
side-channel: 1.1.0 side-channel: 1.1.0
query-selector-shadow-dom@1.0.1: {}
queue-microtask@1.2.3: {} queue-microtask@1.2.3: {}
queue@6.0.2: queue@6.0.2:
@ -16556,6 +16836,8 @@ snapshots:
web-namespaces@2.0.1: {} web-namespaces@2.0.1: {}
web-vitals@5.2.0: {}
webidl-conversions@3.0.1: {} webidl-conversions@3.0.1: {}
webidl-conversions@7.0.0: {} webidl-conversions@7.0.0: {}