diff --git a/apps/dashboard/next.config.mjs b/apps/dashboard/next.config.mjs index dec0ce5..ee02835 100644 --- a/apps/dashboard/next.config.mjs +++ b/apps/dashboard/next.config.mjs @@ -2,7 +2,8 @@ import { fileURLToPath } from 'node:url'; import createJiti from 'jiti'; const jiti = createJiti(fileURLToPath(import.meta.url)); -jiti('./src/config'); +jiti('./src/env/client'); +jiti('./src/env/server'); /** @type {import('next').NextConfig} */ const nextConfig = { @@ -16,6 +17,20 @@ const nextConfig = { ], }, + async rewrites() { + return [ + { + source: '/_proxy/posthog/ingest/static/:path*', + destination: 'https://eu-assets.i.posthog.com/static/:path*', + }, + { + source: '/_proxy/posthog/ingest/:path*', + destination: 'https://eu.i.posthog.com/:path*', + }, + ]; + }, + skipTrailingSlashRedirect: true, + reactStrictMode: true, /** Enables hot reloading for local packages without a build step */ diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index 77d2d3f..47efd32 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -23,6 +23,7 @@ "@octokit/core": "^6.1.2", "@supabase/ssr": "^0.4.0", "@supabase/supabase-js": "^2.45.0", + "@t3-oss/env-core": "^0.11.0", "@t3-oss/env-nextjs": "^0.11.0", "@tanstack/react-query": "^5.51.24", "@trpc/react-query": "catalog:", @@ -37,6 +38,7 @@ "memoize": "^10.0.0", "next": "catalog:", "postgres": "^3.4.4", + "posthog-js": "^1.160.0", "rambda": "^9.2.1", "react": "catalog:", "react-diff-viewer": "^3.1.1", @@ -51,9 +53,9 @@ "zod": "^3.23.8" }, "devDependencies": { - "@ds-project/services": "workspace:*", "@ds-project/eslint": "workspace:*", "@ds-project/prettier": "workspace:*", + "@ds-project/services": "workspace:*", "@ds-project/typescript": "workspace:*", "@next/env": "^14.2.5", "@octokit/types": "^13.5.0", diff --git a/apps/dashboard/src/app/app/tokens/_actions/fetch-release-tokens.action.ts b/apps/dashboard/src/app/app/tokens/_actions/fetch-release-tokens.action.ts index 20b642c..1541b21 100644 --- a/apps/dashboard/src/app/app/tokens/_actions/fetch-release-tokens.action.ts +++ b/apps/dashboard/src/app/app/tokens/_actions/fetch-release-tokens.action.ts @@ -2,9 +2,9 @@ import type { DesignTokens } from 'style-dictionary/types'; import type { Octokit } from '@octokit/core'; -import { config } from '@/config'; import { api } from '@ds-project/api/rsc'; import { getInstallationOctokit } from '@ds-project/services/github'; +import { config } from '@/config'; async function searchFileSha({ octokit, diff --git a/apps/dashboard/src/app/auth/login/_actions/login-user.action.ts b/apps/dashboard/src/app/auth/login/_actions/login-user.action.ts index 0c2a774..b041885 100644 --- a/apps/dashboard/src/app/auth/login/_actions/login-user.action.ts +++ b/apps/dashboard/src/app/auth/login/_actions/login-user.action.ts @@ -1,9 +1,9 @@ 'use server'; import { z } from 'zod'; -import { config } from '@/config'; import { createServerClient } from '@ds-project/auth/server'; import type { Database } from '@ds-project/database'; +import { config } from '@/config'; const schema = z.object({ email: z.string().email(), diff --git a/apps/dashboard/src/app/layout.tsx b/apps/dashboard/src/app/layout.tsx index feaded3..fa6cf85 100644 --- a/apps/dashboard/src/app/layout.tsx +++ b/apps/dashboard/src/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from 'next'; import { Inter } from 'next/font/google'; import './globals.css'; import { cn } from '@/lib/css'; +import { AnalyticsProvider } from '@/lib/analytics/provider'; const inter = Inter({ subsets: ['latin'] }); @@ -17,14 +18,16 @@ export default function RootLayout({ }>) { return ( - - {children} - + + + {children} + + ); } diff --git a/apps/dashboard/src/config.ts b/apps/dashboard/src/config.ts index a6a43d9..7cea628 100644 --- a/apps/dashboard/src/config.ts +++ b/apps/dashboard/src/config.ts @@ -1,63 +1,19 @@ -import { createEnv } from '@t3-oss/env-nextjs'; -import { z } from 'zod'; - -export const env = createEnv({ - server: { - NODE_ENV: z.enum(['development', 'test', 'production']).optional(), - GITHUB_APP_ID: z.string().min(1), - GITHUB_APP_PRIVATE_KEY: z.string().min(1), - GITHUB_APP_CLIENT_ID: z.string().min(1), - GITHUB_APP_CLIENT_SECRET: z.string().min(1), - FIGMA_APP_CLIENT_ID: z.string().min(1), - FIGMA_APP_CLIENT_SECRET: z.string().min(1), - POSTGRES_URL: z.string().min(1), - }, - client: { - NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().min(1), - NEXT_PUBLIC_SUPABASE_URL: z.string().url(), - NEXT_PUBLIC_VERCEL_ENV: z - .enum(['development', 'preview', 'production']) - .optional(), - NEXT_PUBLIC_VERCEL_URL: z.string().min(1).optional(), - }, - experimental__runtimeEnv: { - NEXT_PUBLIC_SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY, - NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL, - NEXT_PUBLIC_VERCEL_ENV: process.env.NEXT_PUBLIC_VERCEL_ENV, - NEXT_PUBLIC_VERCEL_URL: process.env.NEXT_PUBLIC_VERCEL_URL, - }, -}); +import { clientEnv } from './env/client'; const pageUrl = (() => { - switch (env.NEXT_PUBLIC_VERCEL_ENV) { + switch (clientEnv.VERCEL_ENV) { case 'production': return 'https://designsystemproject.pro'; case 'preview': - return `https://${env.NEXT_PUBLIC_VERCEL_URL}`; + return `https://${clientEnv.VERCEL_URL}`; default: return 'http://localhost:3000'; } })(); export const config = { - environment: env.NODE_ENV, pageUrl, - supabaseUrl: env.NEXT_PUBLIC_SUPABASE_URL, - supabaseAnonKey: env.NEXT_PUBLIC_SUPABASE_ANON_KEY, FIGMA_KEY: 'figma.key', - github: { - appId: env.GITHUB_APP_ID, - appPrivateKey: Buffer.from(env.GITHUB_APP_PRIVATE_KEY, 'base64').toString( - 'ascii' - ), - appClientId: env.GITHUB_APP_CLIENT_ID, - appClientSecret: env.GITHUB_APP_CLIENT_SECRET, - }, - databaseUrl: env.NEXT_PUBLIC_SUPABASE_URL, - figma: { - appClientId: env.FIGMA_APP_CLIENT_ID, - appClientSecret: env.FIGMA_APP_CLIENT_SECRET, - redirectUri: `${pageUrl}/integrations/providers/figma/callback`, - }, + figmaRedirectUri: `${pageUrl}/integrations/providers/figma/callback`, gitTokensPath: 'packages/generator/tokens', } as const; diff --git a/apps/dashboard/src/env/client.ts b/apps/dashboard/src/env/client.ts new file mode 100644 index 0000000..eb6e5c7 --- /dev/null +++ b/apps/dashboard/src/env/client.ts @@ -0,0 +1,18 @@ +/* eslint-disable no-restricted-properties */ +import { createEnv } from '@t3-oss/env-nextjs'; +import { z } from 'zod'; +import { vercel } from '@t3-oss/env-core/presets'; + +export const clientEnv = createEnv({ + extends: [vercel()], + client: { + NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().min(1), + NEXT_PUBLIC_SUPABASE_URL: z.string().url(), + NEXT_PUBLIC_POSTHOG_KEY: z.string().min(1), + }, + runtimeEnv: { + NEXT_PUBLIC_SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY, + NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL, + NEXT_PUBLIC_POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY, + }, +}); diff --git a/apps/dashboard/src/env/server.ts b/apps/dashboard/src/env/server.ts new file mode 100644 index 0000000..dfef3b9 --- /dev/null +++ b/apps/dashboard/src/env/server.ts @@ -0,0 +1,18 @@ +/* eslint-disable no-restricted-properties */ +import { createEnv } from '@t3-oss/env-nextjs'; +import { z } from 'zod'; + +export const serverEnv = createEnv({ + isServer: typeof window === 'undefined', + server: { + NODE_ENV: z.enum(['development', 'test', 'production']).optional(), + GITHUB_APP_ID: z.string().min(1), + GITHUB_APP_PRIVATE_KEY: z.string().min(1), + GITHUB_APP_CLIENT_ID: z.string().min(1), + GITHUB_APP_CLIENT_SECRET: z.string().min(1), + FIGMA_APP_CLIENT_ID: z.string().min(1), + FIGMA_APP_CLIENT_SECRET: z.string().min(1), + POSTGRES_URL: z.string().min(1), + }, + experimental__runtimeEnv: process.env, +}); diff --git a/apps/dashboard/src/lib/analytics/provider.tsx b/apps/dashboard/src/lib/analytics/provider.tsx new file mode 100644 index 0000000..71f8282 --- /dev/null +++ b/apps/dashboard/src/lib/analytics/provider.tsx @@ -0,0 +1,20 @@ +'use client'; + +import { clientEnv } from '@/env/client'; +import posthog from 'posthog-js'; +import { PostHogProvider } from 'posthog-js/react'; + +if (typeof window !== 'undefined') { + posthog.init(clientEnv.NEXT_PUBLIC_POSTHOG_KEY, { + api_host: '/_proxy/posthog/ingest', + ui_host: 'https://eu.posthog.com', + person_profiles: 'identified_only', // or 'always' to create profiles for anonymous users as well + }); +} + +interface AnalyticsProviderProps { + children: React.ReactNode; +} +export function AnalyticsProvider({ children }: AnalyticsProviderProps) { + return {children}; +} diff --git a/apps/dashboard/src/lib/auth/middleware.ts b/apps/dashboard/src/lib/auth/middleware.ts index 3d26d07..b6f7d08 100644 --- a/apps/dashboard/src/lib/auth/middleware.ts +++ b/apps/dashboard/src/lib/auth/middleware.ts @@ -1,14 +1,14 @@ import { NextResponse } from 'next/server'; import type { MiddlewareFactory } from '../middleware'; -import { config } from '@/config'; import { createMiddlewareClient } from '@ds-project/auth/middleware'; +import { clientEnv } from '@/env/client'; export const authenticationMiddleware: MiddlewareFactory = (middleware) => async (request, event, response = NextResponse.next({ request })) => { const supabase = createMiddlewareClient(request, response, { - supabaseAnonKey: config.supabaseAnonKey, - supabaseUrl: config.supabaseUrl, + supabaseAnonKey: clientEnv.NEXT_PUBLIC_SUPABASE_ANON_KEY, + supabaseUrl: clientEnv.NEXT_PUBLIC_SUPABASE_URL, }); const { diff --git a/apps/dashboard/src/lib/figma/figma.ts b/apps/dashboard/src/lib/figma/figma.ts index f9c4d34..2dee3a7 100644 --- a/apps/dashboard/src/lib/figma/figma.ts +++ b/apps/dashboard/src/lib/figma/figma.ts @@ -16,6 +16,7 @@ import { import { database } from '@ds-project/database/client'; import { cache } from 'react'; import { api } from '@ds-project/api/rsc'; +import { serverEnv } from '@/env/server'; class Figma { private apiUrl = 'https://api.figma.com'; @@ -47,7 +48,7 @@ class Figma { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, - body: `client_id=${encodeURIComponent(config.figma.appClientId)}&client_secret=${encodeURIComponent(config.figma.appClientSecret)}&redirect_uri=${encodeURIComponent(config.figma.redirectUri)}&code=${encodeURIComponent(code)}&grant_type=authorization_code`, + body: `client_id=${encodeURIComponent(serverEnv.FIGMA_APP_CLIENT_ID)}&client_secret=${encodeURIComponent(serverEnv.FIGMA_APP_CLIENT_SECRET)}&redirect_uri=${encodeURIComponent(config.figmaRedirectUri)}&code=${encodeURIComponent(code)}&grant_type=authorization_code`, }); if (!result.ok) { @@ -86,7 +87,7 @@ class Figma { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, - body: `client_id=${encodeURIComponent(config.figma.appClientId)}&client_secret=${encodeURIComponent(config.figma.appClientSecret)}&redirect_uri=${encodeURIComponent(config.figma.redirectUri)}&refresh_token=${encodeURIComponent(integration.data.refreshToken)}`, + body: `client_id=${encodeURIComponent(serverEnv.FIGMA_APP_CLIENT_ID)}&client_secret=${encodeURIComponent(serverEnv.FIGMA_APP_CLIENT_SECRET)}&redirect_uri=${encodeURIComponent(config.figmaRedirectUri)}&refresh_token=${encodeURIComponent(integration.data.refreshToken)}`, }); if (!result.ok) { @@ -241,7 +242,7 @@ class Figma { } ) ) { - return `${this.url}/oauth?client_id=${config.figma.appClientId}&redirect_uri=${config.figma.redirectUri}&scope=files:read,file_variables:read,file_variables:write&state=${state}&response_type=code`; + return `${this.url}/oauth?client_id=${serverEnv.FIGMA_APP_CLIENT_ID}&redirect_uri=${config.figmaRedirectUri}&scope=files:read,file_variables:read,file_variables:write&state=${state}&response_type=code`; } return null; diff --git a/apps/dashboard/src/lib/middleware/figma/middleware.ts b/apps/dashboard/src/lib/middleware/figma/middleware.ts index 1a8f457..e71b9e9 100644 --- a/apps/dashboard/src/lib/middleware/figma/middleware.ts +++ b/apps/dashboard/src/lib/middleware/figma/middleware.ts @@ -2,10 +2,11 @@ import 'server-only'; import { kv } from '@vercel/kv'; import { NextResponse } from 'next/server'; -import { config } from '@/config'; import type { KVCredentials, KVCredentialsRead } from '@/types/kv-types'; import type { MiddlewareFactory } from '../compose'; import { createMiddlewareClient } from '@ds-project/auth/middleware'; +import { clientEnv } from '@/env/client'; +import { config } from '@/config'; export const figmaMiddleware: MiddlewareFactory = (middleware) => @@ -26,8 +27,8 @@ export const figmaMiddleware: MiddlewareFactory = ); const supabase = createMiddlewareClient(request, response, { - supabaseAnonKey: config.supabaseAnonKey, - supabaseUrl: config.supabaseUrl, + supabaseAnonKey: clientEnv.NEXT_PUBLIC_SUPABASE_ANON_KEY, + supabaseUrl: clientEnv.NEXT_PUBLIC_SUPABASE_URL, }); const { data: { session }, @@ -83,8 +84,8 @@ export const figmaMiddleware: MiddlewareFactory = console.log('🔐 Figma: Figma key detected. Finishing authentication...'); const supabase = createMiddlewareClient(request, response, { - supabaseAnonKey: config.supabaseAnonKey, - supabaseUrl: config.supabaseUrl, + supabaseAnonKey: clientEnv.NEXT_PUBLIC_SUPABASE_ANON_KEY, + supabaseUrl: clientEnv.NEXT_PUBLIC_SUPABASE_URL, }); const { data: { session }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 53a3557..b6a7962 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -108,6 +108,9 @@ importers: '@supabase/supabase-js': specifier: ^2.45.0 version: 2.45.0 + '@t3-oss/env-core': + specifier: ^0.11.0 + version: 0.11.0(typescript@5.5.4)(zod@3.23.8) '@t3-oss/env-nextjs': specifier: ^0.11.0 version: 0.11.0(typescript@5.5.4)(zod@3.23.8) @@ -150,6 +153,9 @@ importers: postgres: specifier: ^3.4.4 version: 3.4.4 + posthog-js: + specifier: ^1.160.0 + version: 1.160.0 rambda: specifier: ^9.2.1 version: 9.2.1 @@ -4844,6 +4850,9 @@ packages: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} + fflate@0.4.8: + resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==} + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -6106,6 +6115,12 @@ packages: resolution: {integrity: sha512-IbyN+9KslkqcXa8AO9fxpk97PA4pzewvpi2B3Dwy9u4zpV32QicaEdgmF3eSQUzdRk7ttDHQejNgAEr4XoeH4A==} engines: {node: '>=12'} + posthog-js@1.160.0: + resolution: {integrity: sha512-K/RRgmPYIpP69nnveCJfkclb8VU+R+jsgqlrKaLGsM5CtQM9g01WOzAiT3u36WLswi58JiFMXgJtECKQuoqTgQ==} + + preact@10.23.2: + resolution: {integrity: sha512-kKYfePf9rzKnxOAKDpsWhg/ysrHPqT+yQ7UW4JjdnqjFIeNUnNcEJvhuA8fDenxAGWzUqtd51DfVg7xp/8T9NA==} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -7208,6 +7223,9 @@ packages: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} + web-vitals@4.2.3: + resolution: {integrity: sha512-/CFAm1mNxSmOj6i0Co+iGFJ58OS4NRGVP+AWS/l509uIK5a1bSoIVaHz/ZumpHTfHSZBpgrJ+wjfpAOrTHok5Q==} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -12011,6 +12029,8 @@ snapshots: node-domexception: 1.0.0 web-streams-polyfill: 3.3.3 + fflate@0.4.8: {} + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -13239,6 +13259,14 @@ snapshots: postgres@3.4.4: {} + posthog-js@1.160.0: + dependencies: + fflate: 0.4.8 + preact: 10.23.2 + web-vitals: 4.2.3 + + preact@10.23.2: {} + prelude-ls@1.2.1: {} prettier-plugin-packagejson@2.5.0(prettier@3.3.3): @@ -14437,6 +14465,8 @@ snapshots: web-streams-polyfill@3.3.3: {} + web-vitals@4.2.3: {} + webidl-conversions@3.0.1: {} webpack-sources@3.2.3: {} diff --git a/turbo.json b/turbo.json index 965487b..1b8b2a9 100644 --- a/turbo.json +++ b/turbo.json @@ -8,7 +8,11 @@ "GITHUB_APP_CLIENT_SECRET", "FIGMA_APP_CLIENT_ID", "FIGMA_APP_CLIENT_SECRET", - "POSTGRES_URL" + "POSTGRES_URL", + "NEXT_PUBLIC_SUPABASE_ANON_KEY", + "NEXT_PUBLIC_SUPABASE_URL", + "NEXT_PUBLIC_POSTHOG_KEY", + "NEXT_PUBLIC_POSTHOG_HOST" ], "tasks": { "topo": {