♻️ Replace yarn with pnpm (#1693)

This commit is contained in:
Luke Vella 2025-04-27 15:16:38 +01:00 committed by GitHub
parent c8e78841f1
commit 765a97f3c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
59 changed files with 17816 additions and 16954 deletions

View file

@ -11,3 +11,11 @@ docker-compose.yml
/docs /docs
README.md README.md
node_modules node_modules
# Build outputs
apps/*/.next
apps/*/dist
packages/*/dist
# Turbo prune output
out/

View file

@ -0,0 +1,8 @@
name: "Pnpm Install"
description: "Runs pnpm install with --frozen-lockfile"
runs:
using: "composite"
steps:
- name: Run pnpm install
run: pnpm install --frozen-lockfile
shell: bash

View file

@ -8,10 +8,13 @@ inputs:
cache: cache:
description: "Package manager for caching" description: "Package manager for caching"
required: false required: false
default: "yarn" default: "pnpm"
runs: runs:
using: "composite" using: "composite"
steps: steps:
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:

View file

@ -1,8 +0,0 @@
name: "Yarn Install"
description: "Runs yarn install with --frozen-lockfile"
runs:
using: "composite"
steps:
- name: Run yarn install
run: yarn install --frozen-lockfile
shell: bash

View file

@ -18,10 +18,13 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: ./.github/actions/setup-node - uses: ./.github/actions/setup-node
- uses: ./.github/actions/yarn-install - uses: ./.github/actions/pnpm-install
- name: Generate Prisma Client
run: pnpm db:generate
- name: Check types - name: Check types
run: yarn type-check run: pnpm type-check
sherif: sherif:
name: Sherif name: Sherif
@ -31,7 +34,7 @@ jobs:
- uses: ./.github/actions/setup-node - uses: ./.github/actions/setup-node
- name: Run sherif - name: Run sherif
run: npx sherif@latest run: pnpm dlx sherif@latest
linting: linting:
name: Linting name: Linting
@ -39,10 +42,10 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: ./.github/actions/setup-node - uses: ./.github/actions/setup-node
- uses: ./.github/actions/yarn-install - uses: ./.github/actions/pnpm-install
- name: Check linting rules - name: Check linting rules
run: yarn lint run: pnpm lint
unit-tests: unit-tests:
name: Unit tests name: Unit tests
@ -50,10 +53,10 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: ./.github/actions/setup-node - uses: ./.github/actions/setup-node
- uses: ./.github/actions/yarn-install - uses: ./.github/actions/pnpm-install
- name: Run tests - name: Run tests
run: yarn test:unit run: pnpm test:unit
# Label of the container job # Label of the container job
integration-tests: integration-tests:
@ -66,25 +69,28 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: ./.github/actions/setup-node - uses: ./.github/actions/setup-node
- uses: ./.github/actions/yarn-install - uses: ./.github/actions/pnpm-install
- name: Generate Prisma Client
run: pnpm db:generate
- name: Install system dependencies - name: Install system dependencies
run: sudo apt-get update run: sudo apt-get update
- name: Install playwright dependencies - name: Install playwright dependencies
run: yarn playwright install --with-deps chromium run: pnpm playwright install --with-deps chromium
- name: Create production build - name: Create production build
run: yarn turbo build:test --filter=@rallly/web run: pnpm turbo build:test --filter=@rallly/web
- name: Start services - name: Start services
run: yarn docker:up run: pnpm docker:up
- name: Setup database - name: Setup database
run: yarn db:deploy run: pnpm db:deploy
- name: Run tests - name: Run tests
run: yarn turbo test:integration run: pnpm turbo test:integration
- name: Upload artifact playwright-report - name: Upload artifact playwright-report
if: ${{ success() || failure() }} if: ${{ success() || failure() }}

2
.gitignore vendored
View file

@ -21,8 +21,6 @@ node_modules
# debug # debug
npm-debug.log* npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files # local env files
.env .env

View file

@ -1,4 +1,4 @@
1. Use yarn for package management 1. Use pnpm for package management
2. Use dayjs for date handling 2. Use dayjs for date handling
3. Use tailwindcss for styling 3. Use tailwindcss for styling
4. Use react-query for data fetching 4. Use react-query for data fetching

View file

@ -38,7 +38,7 @@ The following instructions are for running the project locally for development.
2. Install dependencies 2. Install dependencies
```bash ```bash
yarn pnpm install
``` ```
3. Setup environment variables 3. Setup environment variables
@ -54,7 +54,7 @@ The following instructions are for running the project locally for development.
4. Generate Prisma client 4. Generate Prisma client
```bash ```bash
yarn db:generate pnpm db:generate
``` ```
5. Setup database 5. Setup database
@ -64,13 +64,13 @@ The following instructions are for running the project locally for development.
To start the database, run: To start the database, run:
```bash ```bash
yarn docker:up pnpm docker:up
``` ```
Next run the following command to setup the database: Next run the following command to setup the database:
```bash ```bash
yarn db:reset pnpm db:reset
``` ```
This will: This will:
@ -82,7 +82,7 @@ The following instructions are for running the project locally for development.
6. Start the Next.js server 6. Start the Next.js server
```bash ```bash
yarn dev pnpm dev
``` ```
## Contributors ## Contributors

View file

@ -13,7 +13,7 @@ To preview your changes locally, you can use the [mintlify cli](https://mintlify
Install the cli globally: Install the cli globally:
```bash ```bash
yarn global add mintlify pnpm install --global mintlify
``` ```
Navigate to this directory (where you can find `mint.json`): Navigate to this directory (where you can find `mint.json`):

View file

@ -3,6 +3,6 @@
"version": "0.0.0", "version": "0.0.0",
"private": true, "private": true,
"devDependencies": { "devDependencies": {
"@rallly/tsconfig": "*" "@rallly/tsconfig": "workspace:*"
} }
} }

View file

@ -12,33 +12,42 @@
"i18n:scan": "i18next-scanner --config i18next-scanner.config.js" "i18n:scan": "i18next-scanner --config i18next-scanner.config.js"
}, },
"dependencies": { "dependencies": {
"@rallly/billing": "*", "@prisma/client": "^6.4.1",
"@rallly/icons": "*", "@rallly/billing": "workspace:*",
"@rallly/languages": "*", "@rallly/database": "workspace:*",
"@rallly/tailwind-config": "*", "@rallly/icons": "workspace:*",
"@rallly/ui": "*", "@rallly/languages": "workspace:*",
"@rallly/utils": "*", "@rallly/tailwind-config": "workspace:*",
"@rallly/ui": "workspace:*",
"@rallly/utils": "workspace:*",
"@svgr/webpack": "^6.5.1", "@svgr/webpack": "^6.5.1",
"@vercel/analytics": "^0.1.8", "@vercel/analytics": "^0.1.8",
"dayjs": "^1.11.10", "dayjs": "^1.11.13",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"i18next": "^24.2.2", "i18next": "^24.2.2",
"i18next-icu": "^2.3.0", "i18next-icu": "^2.3.0",
"i18next-resources-to-backend": "^1.2.1", "i18next-resources-to-backend": "^1.2.1",
"intl-messageformat": "^10.7.15", "intl-messageformat": "^10.7.15",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lucide-react": "^0.479.0",
"motion": "^12.6.2",
"nanoid": "^5.0.9", "nanoid": "^5.0.9",
"next": "^14.2.25",
"next-mdx-remote": "^5.0.0", "next-mdx-remote": "^5.0.0",
"next-seo": "^6.1.0", "next-seo": "^6.1.0",
"react-i18next": "^15.4.1", "react": "^18.2.0",
"react-dom": "^18.2.0",
"react-i18next": "^15.5.1",
"react-use": "^17.4.0" "react-use": "^17.4.0"
}, },
"devDependencies": { "devDependencies": {
"@next/bundle-analyzer": "^12.3.4", "@next/bundle-analyzer": "^14.2.25",
"@rallly/eslint-config": "*", "@rallly/eslint-config": "workspace:*",
"@rallly/tsconfig": "*", "@rallly/tsconfig": "workspace:*",
"@types/color-hash": "^1.0.2", "@types/color-hash": "^1.0.2",
"@types/lodash": "^4.14.178", "@types/lodash": "^4.14.178",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"i18next-scanner": "^4.2.0", "i18next-scanner": "^4.2.0",
"i18next-scanner-typescript": "^1.1.1" "i18next-scanner-typescript": "^1.1.1"

View file

@ -19,7 +19,10 @@ export async function middleware(request: NextRequest) {
return; return;
} }
const locale = getPreferredLocale(request); const locale = getPreferredLocale({
acceptLanguageHeader: request.headers.get("accept-language") ?? undefined,
});
request.nextUrl.pathname = `/${locale}${pathname}`; request.nextUrl.pathname = `/${locale}${pathname}`;
if (locale === "en") { if (locale === "en") {

View file

@ -1,5 +1,5 @@
{ {
"installCommand": "yarn install", "installCommand": "pnpm install",
"buildCommand": "cd ../.. && yarn db:generate && yarn build:landing", "buildCommand": "cd ../.. && pnpm db:generate && pnpm build:landing",
"outputDirectory": ".next" "outputDirectory": ".next"
} }

View file

@ -1,22 +1,35 @@
FROM node:20 AS builder # Base stage with Node.js 20 and Corepack enabled
FROM node:20 AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
# Slim base stage with Node.js 20 and Corepack enabled
FROM node:20-slim AS base-slim
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
# --- Builder Stage ---
FROM base AS builder
WORKDIR /app WORKDIR /app
RUN yarn global add turbo COPY package.json .
RUN pnpm add -g turbo
COPY . . COPY . .
RUN turbo prune --scope=@rallly/web --docker RUN turbo prune --scope=@rallly/web --docker
FROM node:20 AS installer # --- Installer Stage ---
FROM base AS installer
WORKDIR /app WORKDIR /app
COPY .gitignore .gitignore COPY package.json .
COPY --from=builder /app/out/json/ . COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/yarn.lock ./yarn.lock COPY --from=builder /app/out/pnpm-lock.yaml ./
RUN yarn --network-timeout 1000000 RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
# Build the project # Build the project
COPY --from=builder /app/out/full/ . COPY --from=builder /app/out/full/ .
COPY turbo.json turbo.json COPY turbo.json turbo.json
RUN yarn db:generate RUN pnpm db:generate
ARG APP_VERSION ARG APP_VERSION
ENV NEXT_PUBLIC_APP_VERSION=$APP_VERSION ENV NEXT_PUBLIC_APP_VERSION=$APP_VERSION
@ -24,11 +37,15 @@ ENV NEXT_PUBLIC_APP_VERSION=$APP_VERSION
ARG SELF_HOSTED ARG SELF_HOSTED
ENV NEXT_PUBLIC_SELF_HOSTED=$SELF_HOSTED ENV NEXT_PUBLIC_SELF_HOSTED=$SELF_HOSTED
RUN SKIP_ENV_VALIDATION=1 yarn build RUN SKIP_ENV_VALIDATION=1 pnpm build
FROM node:20-slim AS runner # --- Runner Stage ---
FROM base-slim AS runner
# prisma requirements # Disable Next.js telemetry for self-hosted instances
ENV NEXT_TELEMETRY_DISABLED=1
# Install required system packages for Prisma
# (see https://www.prisma.io/docs/orm/reference/system-requirements) # (see https://www.prisma.io/docs/orm/reference/system-requirements)
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends \
@ -40,26 +57,36 @@ RUN apt-get update \
WORKDIR /app WORKDIR /app
RUN yarn global add prisma # Install prisma globally needed for runtime operations like migrations
RUN pnpm add -g prisma
# Don't run production as root # Don't run production as root
RUN addgroup --system --gid 1001 nodejs RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs RUN adduser --system --uid 1001 nextjs
# Set HOME env for non-root user
ENV HOME=/app
# Ensure /app is writable by nextjs user AND fix perms for global pnpm dir
RUN chown -R nextjs:nodejs /app /pnpm
USER nextjs USER nextjs
COPY --from=builder --chown=nextjs:nodejs /app/scripts/docker-start.sh ./ # Copy Corepack cache from installer stage to avoid runtime download
COPY --from=builder --chown=nextjs:nodejs /app/packages/database/prisma ./prisma COPY --from=installer /root/.cache/node/corepack/ /app/.cache/node/corepack/
# Copy app files
COPY --from=builder /app/scripts/docker-start.sh ./
COPY --from=builder /app/packages/database/prisma ./prisma
COPY --from=installer /app/apps/web/next.config.js . COPY --from=installer /app/apps/web/next.config.js .
COPY --from=installer /app/apps/web/package.json . COPY --from=installer /app/apps/web/package.json .
ENV PORT=3000 ENV PORT=3000
EXPOSE 3000 EXPOSE 3000
# Automatically leverage output traces to reduce image size # Copy Next.js standalone output
# https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=installer /app/apps/web/.next/standalone ./
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/standalone ./ COPY --from=installer /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static COPY --from=installer /app/apps/web/public ./apps/web/public
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public
ARG SELF_HOSTED ARG SELF_HOSTED
ENV NEXT_PUBLIC_SELF_HOSTED=$SELF_HOSTED ENV NEXT_PUBLIC_SELF_HOSTED=$SELF_HOSTED

View file

@ -8,10 +8,6 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true", enabled: process.env.ANALYZE === "true",
}); });
require("dotenv").config({
path: "../../.env",
});
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
output: output:
@ -100,9 +96,10 @@ const sentryWebpackPluginOptions = {
}; };
const withBundleAnalyzerConfig = withBundleAnalyzer(nextConfig); const withBundleAnalyzerConfig = withBundleAnalyzer(nextConfig);
// Make sure adding Sentry options is the last code to run before exporting, to // Make sure adding Sentry options is the last code to run before exporting, to
// ensure that your source maps include changes from all other Webpack plugins // ensure that your source maps include changes from all other Webpack plugins
module.exports = withSentryConfig( module.exports =
withBundleAnalyzerConfig, process.env.NEXT_PUBLIC_SELF_HOSTED === "true"
sentryWebpackPluginOptions, ? withBundleAnalyzerConfig
); : withSentryConfig(withBundleAnalyzerConfig, sentryWebpackPluginOptions);

View file

@ -3,7 +3,7 @@
"version": "0.0.0", "version": "0.0.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "dotenv -c development next dev",
"build": "next build", "build": "next build",
"build:test": "NODE_ENV=test next build", "build:test": "NODE_ENV=test next build",
"analyze": "cross-env ANALYZE=true next build", "analyze": "cross-env ANALYZE=true next build",
@ -13,7 +13,6 @@
"i18n:scan": "i18next-scanner --config i18next-scanner.config.js", "i18n:scan": "i18next-scanner --config i18next-scanner.config.js",
"test:integration": "NODE_ENV=test playwright test", "test:integration": "NODE_ENV=test playwright test",
"test:unit": "vitest run", "test:unit": "vitest run",
"test": "yarn test:unit && yarn test:e2e",
"test:codegen": "playwright codegen http://localhost:3000", "test:codegen": "playwright codegen http://localhost:3000",
"docker:start": "./scripts/docker-start.sh" "docker:start": "./scripts/docker-start.sh"
}, },
@ -23,20 +22,24 @@
"@aws-sdk/client-s3": "^3.645.0", "@aws-sdk/client-s3": "^3.645.0",
"@aws-sdk/s3-request-presigner": "^3.645.0", "@aws-sdk/s3-request-presigner": "^3.645.0",
"@hookform/resolvers": "^3.3.1", "@hookform/resolvers": "^3.3.1",
"@next/bundle-analyzer": "^12.3.4", "@next/bundle-analyzer": "^14.2.25",
"@next/env": "^15.3.1",
"@panva/hkdf": "^1.2.1", "@panva/hkdf": "^1.2.1",
"@prisma/client": "^6.4.1",
"@radix-ui/react-radio-group": "^1.2.3", "@radix-ui/react-radio-group": "^1.2.3",
"@radix-ui/react-select": "^1.2.1",
"@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-switch": "^1.0.2", "@radix-ui/react-switch": "^1.0.2",
"@rallly/billing": "*", "@rallly/billing": "workspace:*",
"@rallly/database": "*", "@rallly/database": "workspace:*",
"@rallly/emails": "*", "@rallly/emails": "workspace:*",
"@rallly/icons": "*", "@rallly/icons": "workspace:*",
"@rallly/languages": "*", "@rallly/languages": "workspace:*",
"@rallly/posthog": "*", "@rallly/posthog": "workspace:*",
"@rallly/tailwind-config": "*", "@rallly/tailwind-config": "workspace:*",
"@rallly/ui": "*", "@rallly/ui": "workspace:*",
"@sentry/nextjs": "*", "@rallly/utils": "workspace:*",
"@sentry/nextjs": "^8.49.0",
"@svgr/webpack": "^6.5.1", "@svgr/webpack": "^6.5.1",
"@t3-oss/env-nextjs": "^0.11.0", "@t3-oss/env-nextjs": "^0.11.0",
"@tanstack/react-query": "^4.0.0", "@tanstack/react-query": "^4.0.0",
@ -46,7 +49,7 @@
"@trpc/server": "^10.13.0", "@trpc/server": "^10.13.0",
"@upstash/qstash": "^2.7.17", "@upstash/qstash": "^2.7.17",
"@upstash/ratelimit": "^1.2.1", "@upstash/ratelimit": "^1.2.1",
"@vercel/functions": "^1.5.2", "@vercel/functions": "^2.0.0",
"@vercel/kv": "^2.0.0", "@vercel/kv": "^2.0.0",
"ai": "^4.1.50", "ai": "^4.1.50",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
@ -55,7 +58,7 @@
"color-hash": "^2.0.2", "color-hash": "^2.0.2",
"cookie": "^0.7.0", "cookie": "^0.7.0",
"crypto": "^1.0.1", "crypto": "^1.0.1",
"dayjs": "^1.11.10", "dayjs": "^1.11.13",
"i18next": "^24.2.2", "i18next": "^24.2.2",
"i18next-http-backend": "^3.0.2", "i18next-http-backend": "^3.0.2",
"i18next-icu": "^2.3.0", "i18next-icu": "^2.3.0",
@ -72,33 +75,42 @@
"micro": "^10.0.1", "micro": "^10.0.1",
"motion": "^12.6.2", "motion": "^12.6.2",
"nanoid": "^5.0.9", "nanoid": "^5.0.9",
"next": "^14.2.25",
"next-auth": "^5.0.0-beta.25", "next-auth": "^5.0.0-beta.25",
"php-serialize": "^4.1.1", "php-serialize": "^4.1.1",
"postcss": "^8.4.31", "postcss": "^8.4.31",
"react": "^18.2.0",
"react-big-calendar": "^1.8.1", "react-big-calendar": "^1.8.1",
"react-dom": "^18.2.0",
"react-hook-form": "^7.42.1", "react-hook-form": "^7.42.1",
"react-hook-form-persist": "^3.0.0", "react-hook-form-persist": "^3.0.0",
"react-i18next": "^15.4.1", "react-i18next": "^15.5.1",
"react-remove-scroll": "^2.5.6", "react-remove-scroll": "^2.5.6",
"react-use": "^17.4.0", "react-use": "^17.4.0",
"smoothscroll-polyfill": "^0.4.4", "smoothscroll-polyfill": "^0.4.4",
"spacetime": "^7.4.7", "spacetime": "^7.4.7",
"superjson": "^2.0.0", "superjson": "^2.0.0",
"timezone-soft": "^1.5.1" "tailwindcss": "^3.4.17",
"timezone-soft": "^1.5.1",
"zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.26.10",
"@playwright/test": "^1.49.1", "@playwright/test": "^1.49.1",
"@rallly/eslint-config": "*", "@rallly/eslint-config": "workspace:*",
"@rallly/tsconfig": "*", "@rallly/tsconfig": "workspace:*",
"@types/color-hash": "^1.0.2", "@types/color-hash": "^1.0.2",
"@types/js-cookie": "^3.0.1",
"@types/lodash": "^4.14.178", "@types/lodash": "^4.14.178",
"@types/node": "^18.19.41",
"@types/react": "^18.2.0",
"@types/react-big-calendar": "^1.8.8", "@types/react-big-calendar": "^1.8.8",
"@types/react-dom": "^18.2.0",
"@types/smoothscroll-polyfill": "^0.3.1", "@types/smoothscroll-polyfill": "^0.3.1",
"cheerio": "^1.0.0-rc.12", "cheerio": "^1.0.0-rc.12",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"i18next-scanner": "^4.2.0", "i18next-scanner": "^4.2.0",
"i18next-scanner-typescript": "^1.1.1", "i18next-scanner-typescript": "^1.1.1",
"vitest": "^2.1.9", "vitest": "^2.1.9"
"wait-on": "^6.0.1"
} }
} }

View file

@ -1,4 +1,5 @@
import { Tile, TileGrid, TileTitle } from "@rallly/ui/tile"; import { Tile, TileGrid, TileTitle } from "@rallly/ui/tile";
import Link from "next/link";
import type { Params } from "@/app/[locale]/types"; import type { Params } from "@/app/[locale]/types";
import { import {
@ -56,18 +57,22 @@ export default async function Page({ params }: { params: Params }) {
<Trans i18nKey="homeNavTitle" defaults="Navigation" /> <Trans i18nKey="homeNavTitle" defaults="Navigation" />
</h2> </h2>
<TileGrid> <TileGrid>
<Tile href="/polls"> <Tile asChild>
<PollPageIcon /> <Link href="/polls">
<TileTitle> <PollPageIcon />
<Trans i18nKey="polls" defaults="Polls" /> <TileTitle>
</TileTitle> <Trans i18nKey="polls" defaults="Polls" />
</TileTitle>
</Link>
</Tile> </Tile>
<Tile href="/events"> <Tile asChild>
<EventPageIcon /> <Link href="/events">
<TileTitle> <EventPageIcon />
<Trans i18nKey="events" defaults="Events" /> <TileTitle>
</TileTitle> <Trans i18nKey="events" defaults="Events" />
</TileTitle>
</Link>
</Tile> </Tile>
{/* <Tile href="/members"> {/* <Tile href="/members">
@ -84,25 +89,31 @@ export default async function Page({ params }: { params: Params }) {
<Trans i18nKey="account" defaults="Account" /> <Trans i18nKey="account" defaults="Account" />
</h2> </h2>
<TileGrid> <TileGrid>
<Tile href="/settings/profile"> <Tile asChild>
<ProfilePageIcon /> <Link href="/settings/profile">
<TileTitle> <ProfilePageIcon />
<Trans i18nKey="profile" defaults="Profile" /> <TileTitle>
</TileTitle> <Trans i18nKey="profile" defaults="Profile" />
</TileTitle>
</Link>
</Tile> </Tile>
<Tile href="/settings/preferences"> <Tile asChild>
<PreferencesPageIcon /> <Link href="/settings/preferences">
<TileTitle> <PreferencesPageIcon />
<Trans i18nKey="preferences" defaults="Preferences" /> <TileTitle>
</TileTitle> <Trans i18nKey="preferences" defaults="Preferences" />
</TileTitle>
</Link>
</Tile> </Tile>
<Tile href="/settings/billing"> <Tile asChild>
<BillingPageIcon /> <Link href="/settings/billing">
<TileTitle> <BillingPageIcon />
<Trans i18nKey="billing" defaults="Billing" /> <TileTitle>
</TileTitle> <Trans i18nKey="billing" defaults="Billing" />
</TileTitle>
</Link>
</Tile> </Tile>
</TileGrid> </TileGrid>
</div> </div>

View file

@ -1,8 +1,7 @@
import "tailwindcss/tailwind.css";
import "../../style.css"; import "../../style.css";
import { defaultLocale, supportedLngs } from "@rallly/languages"; import { defaultLocale, supportedLngs } from "@rallly/languages";
import { PostHogProvider } from "@rallly/posthog/client"; import { posthog, PostHogProvider } from "@rallly/posthog/client";
import { Toaster } from "@rallly/ui/toaster"; import { Toaster } from "@rallly/ui/toaster";
import { TooltipProvider } from "@rallly/ui/tooltip"; import { TooltipProvider } from "@rallly/ui/tooltip";
import { domAnimation, LazyMotion } from "motion/react"; import { domAnimation, LazyMotion } from "motion/react";
@ -61,7 +60,7 @@ export default async function Root({
<I18nProvider locale={locale}> <I18nProvider locale={locale}>
<TRPCProvider> <TRPCProvider>
<LazyMotion features={domAnimation}> <LazyMotion features={domAnimation}>
<PostHogProvider> <PostHogProvider client={posthog}>
<PostHogPageView /> <PostHogPageView />
<TooltipProvider> <TooltipProvider>
<UserProvider <UserProvider

View file

@ -1,6 +1,5 @@
import { withPosthog } from "@rallly/posthog/server";
import { handlers } from "@/next-auth"; import { handlers } from "@/next-auth";
import { withPosthog } from "@/utils/posthog";
export const GET = withPosthog(handlers.GET); export const GET = withPosthog(handlers.GET);
export const POST = withPosthog(handlers.POST); export const POST = withPosthog(handlers.POST);

View file

@ -1,10 +1,11 @@
import type { Stripe } from "@rallly/billing"; import type { Stripe } from "@rallly/billing";
import { stripe } from "@rallly/billing"; import { stripe } from "@rallly/billing";
import { withPosthog } from "@rallly/posthog/server";
import * as Sentry from "@sentry/nextjs"; import * as Sentry from "@sentry/nextjs";
import type { NextRequest } from "next/server"; import type { NextRequest } from "next/server";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { withPosthog } from "@/utils/posthog";
import { getEventHandler } from "./handlers"; import { getEventHandler } from "./handlers";
export const POST = withPosthog(async (request: NextRequest) => { export const POST = withPosthog(async (request: NextRequest) => {

View file

@ -19,7 +19,9 @@ const handler = async (req: NextRequest) => {
req, req,
router: appRouter, router: appRouter,
createContext: async () => { createContext: async () => {
const locale = getPreferredLocale(req); const locale = getPreferredLocale({
acceptLanguageHeader: req.headers.get("accept-language") ?? undefined,
});
const user = session?.user const user = session?.user
? { ? {
id: session.user.id, id: session.user.id,

View file

@ -1,6 +1,6 @@
"use client"; "use client";
import { usePostHog } from "@rallly/posthog/client";
import { usePathname, useSearchParams } from "next/navigation"; import { usePathname, useSearchParams } from "next/navigation";
import { usePostHog } from "posthog-js/react";
import { useEffect } from "react"; import { useEffect } from "react";
export function PostHogPageView() { export function PostHogPageView() {

View file

@ -1,6 +1,6 @@
import languages from "@rallly/languages"; import languages from "@rallly/languages";
import { getPreferredLocale } from "@rallly/languages/get-preferred-locale"; import { getPreferredLocale } from "@rallly/languages/get-preferred-locale";
import { withPostHog } from "@rallly/posthog/next/middleware"; import { getPosthogBootstrapCookie } from "@rallly/posthog/utils";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { withAuth } from "@/auth/edge"; import { withAuth } from "@/auth/edge";
@ -19,7 +19,12 @@ export const middleware = withAuth(async (req) => {
return NextResponse.redirect(newUrl); return NextResponse.redirect(newUrl);
} }
const locale = req.auth?.user?.locale || getPreferredLocale(req); const locale =
req.auth?.user?.locale ||
getPreferredLocale({
acceptLanguageHeader: req.headers.get("accept-language") ?? undefined,
});
if (supportedLocales.includes(locale)) { if (supportedLocales.includes(locale)) {
newUrl.pathname = `/${locale}${pathname}`; newUrl.pathname = `/${locale}${pathname}`;
} }
@ -29,7 +34,12 @@ export const middleware = withAuth(async (req) => {
res.headers.set("x-pathname", pathname); res.headers.set("x-pathname", pathname);
if (req.auth?.user?.id) { if (req.auth?.user?.id) {
await withPostHog(res, { distinctID: req.auth.user.id }); const bootstrapCookie = getPosthogBootstrapCookie({
distinctID: req.auth.user.id,
});
if (bootstrapCookie) {
res.cookies.set(bootstrapCookie);
}
} }
return res; return res;

View file

@ -0,0 +1,15 @@
import { posthog } from "@rallly/posthog/server";
import { waitUntil } from "@vercel/functions";
import type { NextRequest } from "next/server";
export function withPosthog(handler: (req: NextRequest) => Promise<Response>) {
return async (req: NextRequest) => {
const res = await handler(req);
try {
waitUntil(Promise.all([posthog?.shutdown()]));
} catch (error) {
console.error("Failed to flush PostHog events:", error);
}
return res;
};
}

View file

@ -3,10 +3,15 @@
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["src/*"] "@/*": [
"src/*"
]
}, },
"strictNullChecks": true, "strictNullChecks": true,
"types": ["vitest/globals"] "types": [
"vitest/globals"
],
"target": "ES2017"
}, },
"include": [ "include": [
"**/*.ts", "**/*.ts",
@ -15,5 +20,10 @@
".next/types/**/*.ts", ".next/types/**/*.ts",
"vitest.config.mts" "vitest.config.mts"
], ],
"exclude": ["node_modules", ".next/**/*", "playwright-report", "test-results"] "exclude": [
"node_modules",
".next/**/*",
"playwright-report",
"test-results"
]
} }

View file

@ -1,5 +1,5 @@
{ {
"installCommand": "yarn install", "installCommand": "pnpm install",
"buildCommand": "cd ../.. && yarn db:generate && yarn build:web && yarn db:deploy", "buildCommand": "cd ../.. && pnpm db:generate && pnpm build:web && pnpm db:deploy",
"outputDirectory": ".next" "outputDirectory": ".next"
} }

View file

@ -17,7 +17,7 @@
"db:migrate": "prisma migrate dev", "db:migrate": "prisma migrate dev",
"db:reset": "prisma migrate reset", "db:reset": "prisma migrate reset",
"db:push": "prisma db push", "db:push": "prisma db push",
"docker:up": "docker compose -f docker-compose.dev.yml up -d && wait-on --timeout 60000 tcp:localhost:5450", "docker:up": "docker compose -f docker-compose.dev.yml up -d && pnpm dlx wait-on --timeout 60000 tcp:localhost:5450",
"docker:down": "docker compose -f docker-compose.dev.yml down --volumes --remove-orphans", "docker:down": "docker compose -f docker-compose.dev.yml down --volumes --remove-orphans",
"test:integration": "turbo test:integration", "test:integration": "turbo test:integration",
"test:unit": "turbo test:unit", "test:unit": "turbo test:unit",
@ -30,33 +30,22 @@
"sherif:fix": "npx sherif@latest --fix" "sherif:fix": "npx sherif@latest --fix"
}, },
"prisma": { "prisma": {
"seed": "yarn workspace @rallly/database db:seed", "seed": "pnpm --filter @rallly/database db:seed",
"schema": "./packages/database/prisma/schema.prisma" "schema": "./packages/database/prisma/schema.prisma"
}, },
"workspaces": [
"apps/*",
"packages/*"
],
"devDependencies": { "devDependencies": {
"@prisma/client": "^6.4.1", "@playwright/test": "^1.49.1",
"@sentry/nextjs": "^8.49.0", "dotenv-cli": "^8.0.0",
"@types/react": "^18.2.48", "eslint": "^8.52.0",
"@types/react-dom": "^18.2.18",
"dotenv-cli": "^7.1.0",
"next": "^14.2.25",
"npm-run-all": "^4.1.5",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.8", "prettier-plugin-tailwindcss": "^0.6.8",
"react": "^18.2.0", "prisma": "^6.4.1",
"react-dom": "^18.2.0",
"tailwindcss": "^3.4.17",
"turbo": "^2.4.4", "turbo": "^2.4.4",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^2.1.9", "vitest": "^2.1.9"
"zod": "^3.23.8"
}, },
"engines": { "engines": {
"node": "20.x" "node": "20.x"
}, },
"packageManager": "yarn@1.22.22" "packageManager": "pnpm@10.9.0"
} }

View file

@ -15,11 +15,12 @@
}, },
"dependencies": { "dependencies": {
"@radix-ui/react-radio-group": "^1.2.3", "@radix-ui/react-radio-group": "^1.2.3",
"@rallly/ui": "*", "@rallly/database": "workspace:*",
"next": "*", "@rallly/ui": "workspace:*",
"stripe": "^13.2.0" "stripe": "^13.2.0"
}, },
"devDependencies": { "devDependencies": {
"@rallly/eslint-config": "*" "@rallly/eslint-config": "workspace:*",
"@rallly/tsconfig": "workspace:*"
} }
} }

View file

@ -11,11 +11,15 @@
"type-check": "tsc --pretty --noEmit" "type-check": "tsc --pretty --noEmit"
}, },
"exports": "./index.ts", "exports": "./index.ts",
"dependencies": {
"@prisma/client": "^6.4.1",
"dayjs": "^1.11.13"
},
"devDependencies": { "devDependencies": {
"@faker-js/faker": "^7.6.0", "@faker-js/faker": "^7.6.0",
"@rallly/eslint-config": "*", "@rallly/eslint-config": "workspace:*",
"@rallly/tsconfig": "*", "@rallly/tsconfig": "workspace:*",
"@types/node": "^18.15.10", "@types/node": "^18.19.41",
"prisma": "^6.4.1", "prisma": "^6.4.1",
"tsx": "^4.6.2" "tsx": "^4.6.2"
} }

View file

@ -1,27 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@prisma/client@^4.10.1":
version "4.10.1"
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-4.10.1.tgz#c47fd54661ee74b174cee63e9dc418ecf57a6ccd"
integrity sha512-VonXLJZybdt8e5XZH5vnIGCRNnIh6OMX1FS3H/yzMGLT3STj5TJ/OkMcednrvELgk8PK89Vo3aSh51MWNO0axA==
dependencies:
"@prisma/engines-version" "4.10.1-2.aead147aa326ccb985dcfed5b065b4fdabd44b19"
"@prisma/engines-version@4.10.1-2.aead147aa326ccb985dcfed5b065b4fdabd44b19":
version "4.10.1-2.aead147aa326ccb985dcfed5b065b4fdabd44b19"
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-4.10.1-2.aead147aa326ccb985dcfed5b065b4fdabd44b19.tgz#312359d9d00e39e323136d0270876293d315658e"
integrity sha512-tsjTho7laDhf9EJ9EnDxAPEf7yrigSMDhniXeU4YoWc7azHAs4GPxRi2P9LTFonmHkJLMOLjR77J1oIP8Ife1w==
"@prisma/engines@4.10.1":
version "4.10.1"
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-4.10.1.tgz#c7062747f254e5d5fce98a8cae566c25f9f29fb2"
integrity sha512-B3tcTxjx196nuAu1GOTKO9cGPUgTFHYRdkPkTS4m5ptb2cejyBlH9X7GOfSt3xlI7p4zAJDshJP4JJivCg9ouA==
prisma@^4.10.1:
version "4.10.1"
resolved "https://registry.yarnpkg.com/prisma/-/prisma-4.10.1.tgz#88084695d7b364ae6bebf93d5006f84439c4e7d1"
integrity sha512-0jDxgg+DruB1kHVNlcspXQB9au62IFfVg9drkhzXudszHNUAQn0lVuu+T8np0uC2z1nKD5S3qPeCyR8u5YFLnA==
dependencies:
"@prisma/engines" "4.10.1"

View file

@ -16,15 +16,21 @@
"@aws-sdk/client-ses": "^3.501.0", "@aws-sdk/client-ses": "^3.501.0",
"@aws-sdk/credential-provider-node": "^3.501.0", "@aws-sdk/credential-provider-node": "^3.501.0",
"@react-email/components": "^0.0.14", "@react-email/components": "^0.0.14",
"@react-email/render": "^0.0.12", "@react-email/render": "^1.0.6",
"@vercel/functions": "*", "@vercel/functions": "^2.0.0",
"i18next": "^24.2.2",
"i18next-icu": "^2.3.0",
"i18next-resources-to-backend": "^1.2.1",
"nodemailer": "^6.9.9", "nodemailer": "^6.9.9",
"react-email": "^4.0.2" "react": "^18.2.0",
"react-dom": "^18.2.0",
"react-email": "^4.0.2",
"react-i18next": "^15.5.1"
}, },
"devDependencies": { "devDependencies": {
"@rallly/tailwind-config": "*", "@rallly/tailwind-config": "workspace:*",
"@rallly/tsconfig": "*", "@rallly/tsconfig": "workspace:*",
"@rallly/utils": "*", "@rallly/utils": "workspace:*",
"@types/nodemailer": "^6.4.14" "@types/nodemailer": "^6.4.14"
} }
} }

View file

@ -1,27 +0,0 @@
# React Email Starter
A live preview right in your browser so you don't need to keep sending real emails during development.
## Getting Started
First, install the dependencies:
```sh
npm install
# or
yarn
```
Then, run the development server:
```sh
npm run dev
# or
yarn dev
```
Open [localhost:3000](http://localhost:3000) with your browser to see the result.
## License
MIT License

View file

@ -1,4 +1,4 @@
import { Section } from "@react-email/section"; import { Section } from "@react-email/components";
import { Trans } from "react-i18next/TransWithoutContext"; import { Trans } from "react-i18next/TransWithoutContext";
import type { EmailContext } from "../types"; import type { EmailContext } from "../types";

View file

@ -1,11 +0,0 @@
import { Client } from "@upstash/qstash";
export function createQstashClient() {
if (!process.env.QSTASH_TOKEN) {
return null;
}
return new Client({
token: process.env.QSTASH_TOKEN,
});
}

View file

@ -1,4 +1,4 @@
import { Section } from "@react-email/section"; import { Section } from "@react-email/components";
import { Trans } from "react-i18next/TransWithoutContext"; import { Trans } from "react-i18next/TransWithoutContext";
import { EmailLayout } from "../components/email-layout"; import { EmailLayout } from "../components/email-layout";

View file

@ -1,4 +1,4 @@
import { Section } from "@react-email/section"; import { Section } from "@react-email/components";
import { Trans } from "react-i18next/TransWithoutContext"; import { Trans } from "react-i18next/TransWithoutContext";
import { EmailLayout } from "../components/email-layout"; import { EmailLayout } from "../components/email-layout";

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,6 @@
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.28.0", "@typescript-eslint/eslint-plugin": "^8.28.0",
"@typescript-eslint/parser": "^8.28.0", "@typescript-eslint/parser": "^8.28.0",
"eslint": "^8.52.0",
"eslint-config-next": "^14.0.1", "eslint-config-next": "^14.0.1",
"eslint-config-turbo": "^2.0.3", "eslint-config-turbo": "^2.0.3",
"eslint-import-resolver-typescript": "^2.7.0", "eslint-import-resolver-typescript": "^2.7.0",

View file

@ -7,5 +7,9 @@
"dependencies": { "dependencies": {
"@heroicons/react": "^1.0.6", "@heroicons/react": "^1.0.6",
"lucide-react": "^0.479.0" "lucide-react": "^0.479.0"
},
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
} }
} }

View file

@ -11,6 +11,7 @@
"negotiator": "^1.0.0" "negotiator": "^1.0.0"
}, },
"devDependencies": { "devDependencies": {
"@rallly/tsconfig": "workspace:*",
"@types/negotiator": "^0.6.3" "@types/negotiator": "^0.6.3"
} }
} }

View file

@ -1,14 +1,21 @@
import languages, { defaultLocale } from "./index"; import languages, { defaultLocale } from "./index";
import Negotiator from "negotiator"; import Negotiator from "negotiator";
import { match } from "@formatjs/intl-localematcher"; import { match } from "@formatjs/intl-localematcher";
import type { NextRequest } from "next/server";
const locales = Object.keys(languages); const locales = Object.keys(languages);
export function getPreferredLocale(req: NextRequest) { export function getPreferredLocale({
acceptLanguageHeader,
}: {
acceptLanguageHeader?: string;
}) {
if (!acceptLanguageHeader) {
return defaultLocale;
}
const preferredLanguages = new Negotiator({ const preferredLanguages = new Negotiator({
headers: { headers: {
"accept-language": req.headers.get("accept-language") ?? "", "accept-language": acceptLanguageHeader,
}, },
}) })
.languages() .languages()

View file

@ -1,5 +1,5 @@
{ {
"extends": "@rallly/tsconfig/react.json", "extends": "@rallly/tsconfig/node.json",
"include": ["**/*.ts"], "include": ["src/**/*.ts"],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }

View file

@ -4,23 +4,21 @@
"private": true, "private": true,
"exports": { "exports": {
"./server": "./src/server/index.ts", "./server": "./src/server/index.ts",
"./client": "./src/client/index.ts", "./client": "./src/client.ts",
"./next/middleware": "./src/next/middleware.ts" "./utils": "./src/utils.ts"
}, },
"scripts": { "scripts": {
"lint": "eslint ./src", "lint": "eslint ./src",
"type-check": "tsc --noEmit" "type-check": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"js-cookie": "^3.0.1",
"posthog-js": "^1.234.1", "posthog-js": "^1.234.1",
"posthog-node": "^4.10.2" "posthog-node": "^4.10.2"
}, },
"devDependencies": { "devDependencies": {
"@rallly/eslint-config": "*", "@rallly/eslint-config": "workspace:*",
"@rallly/tsconfig": "*" "@rallly/tsconfig": "workspace:*",
}, "@types/js-cookie": "^3.0.1"
"peerDependencies": {
"next": "^14.2.13",
"react": "^18.2.0"
} }
} }

View file

@ -1,10 +1,10 @@
"use client"; "use client";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import posthog from "posthog-js"; import posthog from "posthog-js";
import { PostHogProvider as Provider } from "posthog-js/react";
import React from "react";
import { POSTHOG_BOOTSTAP_DATA_COOKIE_NAME } from "../constants"; import { POSTHOG_BOOTSTAP_DATA_COOKIE_NAME } from "./constants";
export { PostHogProvider, usePostHog } from "posthog-js/react";
if (typeof window !== "undefined" && process.env.NEXT_PUBLIC_POSTHOG_API_KEY) { if (typeof window !== "undefined" && process.env.NEXT_PUBLIC_POSTHOG_API_KEY) {
let bootstrapData = {}; let bootstrapData = {};
@ -31,6 +31,4 @@ if (typeof window !== "undefined" && process.env.NEXT_PUBLIC_POSTHOG_API_KEY) {
}); });
} }
export function PostHogProvider(props: { children?: React.ReactNode }) { export { posthog };
return <Provider client={posthog}>{props.children}</Provider>;
}

View file

@ -1,4 +0,0 @@
"use client";
export { PostHogProvider } from "./provider";
export { usePostHog } from "posthog-js/react";

View file

@ -1,23 +0,0 @@
import type { NextResponse } from "next/server";
import { POSTHOG_BOOTSTAP_DATA_COOKIE_NAME } from "../constants";
const posthogApiKey = process.env.NEXT_PUBLIC_POSTHOG_API_KEY;
export async function withPostHog(
res: NextResponse,
bootstrapData: { distinctID?: string },
) {
if (!posthogApiKey) {
return;
}
res.cookies.set({
name: POSTHOG_BOOTSTAP_DATA_COOKIE_NAME,
value: JSON.stringify(bootstrapData),
httpOnly: false,
secure: true,
sameSite: "lax",
path: "/",
});
}

View file

@ -1,5 +1,3 @@
import { waitUntil } from "@vercel/functions";
import type { NextRequest } from "next/server";
import { PostHog } from "posthog-node"; import { PostHog } from "posthog-node";
function PostHogClient() { function PostHogClient() {
@ -14,15 +12,3 @@ function PostHogClient() {
} }
export const posthog = PostHogClient(); export const posthog = PostHogClient();
export function withPosthog(handler: (req: NextRequest) => Promise<Response>) {
return async (req: NextRequest) => {
const res = await handler(req);
try {
waitUntil(Promise.all([posthog?.shutdown()]));
} catch (error) {
console.error("Failed to flush PostHog events:", error);
}
return res;
};
}

View file

@ -0,0 +1,20 @@
import { POSTHOG_BOOTSTAP_DATA_COOKIE_NAME } from "./constants";
const posthogApiKey = process.env.NEXT_PUBLIC_POSTHOG_API_KEY;
export function getPosthogBootstrapCookie(bootstrapData: {
distinctID?: string;
}) {
if (!posthogApiKey) {
return;
}
return {
name: POSTHOG_BOOTSTAP_DATA_COOKIE_NAME,
value: JSON.stringify(bootstrapData),
httpOnly: false,
secure: true,
sameSite: "lax" as const,
path: "/",
};
}

View file

@ -4,6 +4,9 @@
"private": true, "private": true,
"main": "tailwind.config.js", "main": "tailwind.config.js",
"types": "tailwind.config.d.ts", "types": "tailwind.config.d.ts",
"dependencies": {
"tailwindcss": "^3.4.17"
},
"devDependencies": { "devDependencies": {
"@tailwindcss/typography": "^0.5.13", "@tailwindcss/typography": "^0.5.13",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",

View file

@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Node.js",
"compilerOptions": {
"lib": ["es2020"],
"module": "NodeNext",
"target": "es2020",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "NodeNext",
"noUncheckedIndexedAccess": true
}
}

View file

@ -24,6 +24,7 @@
"@radix-ui/react-dropdown-menu": "^2.0.4", "@radix-ui/react-dropdown-menu": "^2.0.4",
"@radix-ui/react-label": "^2.0.1", "@radix-ui/react-label": "^2.0.1",
"@radix-ui/react-popover": "^1.0.5", "@radix-ui/react-popover": "^1.0.5",
"@radix-ui/react-portal": "^1.1.6",
"@radix-ui/react-progress": "^1.1.2", "@radix-ui/react-progress": "^1.1.2",
"@radix-ui/react-radio-group": "^1.2.3", "@radix-ui/react-radio-group": "^1.2.3",
"@radix-ui/react-select": "^1.2.1", "@radix-ui/react-select": "^1.2.1",
@ -33,17 +34,22 @@
"@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.4", "@radix-ui/react-toast": "^1.1.4",
"@radix-ui/react-tooltip": "^1.1.8", "@radix-ui/react-tooltip": "^1.1.8",
"@rallly/icons": "*", "@rallly/icons": "workspace:*",
"@rallly/languages": "*", "@rallly/languages": "workspace:*",
"@rallly/tailwind-config": "*", "@rallly/tailwind-config": "workspace:*",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"cmdk": "^0.2.1", "cmdk": "^0.2.1",
"lucide-react": "^0.479.0", "lucide-react": "^0.479.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.42.1",
"tailwind-merge": "^1.12.0" "tailwind-merge": "^1.12.0"
}, },
"devDependencies": { "devDependencies": {
"@rallly/eslint-config": "*", "@rallly/eslint-config": "workspace:*",
"@rallly/tsconfig": "*" "@rallly/tsconfig": "workspace:*",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0"
} }
} }

View file

@ -1,26 +1,32 @@
"use client"; "use client";
import { Slot } from "@radix-ui/react-slot"; import { Slot } from "@radix-ui/react-slot";
import Link from "next/link";
import * as React from "react"; import * as React from "react";
import { cn } from "./lib/utils"; import { cn } from "./lib/utils";
const Tile = React.forwardRef< const Tile = React.forwardRef<
HTMLAnchorElement, HTMLDivElement,
React.ComponentPropsWithoutRef<typeof Link> {
>(({ className, children, ...props }, ref) => ( className?: string;
<Link children?: React.ReactNode;
ref={ref} asChild?: boolean;
className={cn( }
"text-card-foreground bg-background flex flex-col justify-end rounded-xl border p-3 shadow-sm transition-shadow hover:bg-gray-50 active:shadow-none", >(({ className, asChild, children, ...props }, ref) => {
className, const Comp = asChild ? Slot : "div";
)} return (
{...props} <Comp
> ref={ref}
{children} className={cn(
</Link> "text-card-foreground bg-background flex flex-col justify-end rounded-xl border p-3 shadow-sm transition-shadow hover:bg-gray-50 active:shadow-none",
)); className,
)}
{...props}
>
{children}
</Comp>
);
});
Tile.displayName = "Tile"; Tile.displayName = "Tile";
const TileIcon = React.forwardRef< const TileIcon = React.forwardRef<

View file

@ -14,6 +14,8 @@
"nanoid": "^5.0.9" "nanoid": "^5.0.9"
}, },
"devDependencies": { "devDependencies": {
"@rallly/tsconfig": "workspace:*",
"@types/node": "^18.19.41",
"vitest": "^2.1.9" "vitest": "^2.1.9"
} }
} }

View file

@ -1,7 +1,8 @@
{ {
"compilerOptions": { "compilerOptions": {
"types": ["vitest/globals"] "types": ["vitest/globals", "node"],
"lib": ["es2020", "dom"]
}, },
"extends": "@rallly/tsconfig/react.json", "extends": "@rallly/tsconfig/node.json",
"include": ["**/*.ts", "**/*.tsx"] "include": ["**/*.ts"]
} }

17428
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load diff

3
pnpm-workspace.yaml Normal file
View file

@ -0,0 +1,3 @@
packages:
- 'apps/*'
- 'packages/*'

View file

@ -4,5 +4,5 @@ set -e
export DIRECT_DATABASE_URL=$DATABASE_URL export DIRECT_DATABASE_URL=$DATABASE_URL
export AUTH_URL=$NEXT_PUBLIC_BASE_URL export AUTH_URL=$NEXT_PUBLIC_BASE_URL
prisma migrate deploy --schema=./prisma/schema.prisma pnpm prisma migrate deploy --schema=./prisma/schema.prisma
node apps/web/server.js node apps/web/server.js

14644
yarn.lock

File diff suppressed because it is too large Load diff