diff --git a/login/apps/login/.eslintrc.standalone.cjs b/login/apps/login/.eslintrc.standalone.cjs new file mode 100644 index 0000000000..7f1ccb61d1 --- /dev/null +++ b/login/apps/login/.eslintrc.standalone.cjs @@ -0,0 +1,7 @@ +module.exports = { + extends: ["next/core-web-vitals"], + rules: { + "@next/next/no-img-element": "off", + "react/no-unescaped-entities": "off" + } +}; diff --git a/login/apps/login/.github/workflows/ci.yml b/login/apps/login/.github/workflows/ci.yml new file mode 100644 index 0000000000..859421f587 --- /dev/null +++ b/login/apps/login/.github/workflows/ci.yml @@ -0,0 +1,58 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [18.x, 20.x] + + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Prepare standalone + run: ./prepare-standalone.sh + + - name: Run tests + run: npm run test:unit + + - name: Run linting + run: npm run lint + + - name: Build application + run: npm run build:standalone + + build-docker: + needs: test + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + + steps: + - uses: actions/checkout@v4 + + - name: Prepare standalone + run: ./prepare-standalone.sh + + - name: Build Docker image + run: | + docker build -t zitadel-login-ui . + + - name: Test Docker image + run: | + docker run --rm -d -p 3000:3000 --name test-container zitadel-login-ui + sleep 10 + curl -f http://localhost:3000 || exit 1 + docker stop test-container diff --git a/login/apps/login/.gitignore.standalone b/login/apps/login/.gitignore.standalone new file mode 100644 index 0000000000..2cfd1482b6 --- /dev/null +++ b/login/apps/login/.gitignore.standalone @@ -0,0 +1,54 @@ +# Dependencies +/node_modules +/.pnp +.pnp.js + +# Testing +/coverage + +# Next.js +/.next/ +/out/ + +# Production +/build + +# Misc +.DS_Store +*.pem + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Local env files +.env*.local + +# Vercel +.vercel + +# TypeScript +*.tsbuildinfo +next-env.d.ts + +# Turbo +.turbo + +# IDE +.vscode/ +.idea/ + +# Logs +logs +*.log + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Package manager locks (we use npm in standalone) +pnpm-lock.yaml +yarn.lock diff --git a/login/apps/login/Dockerfile b/login/apps/login/Dockerfile new file mode 100644 index 0000000000..06bd053076 --- /dev/null +++ b/login/apps/login/Dockerfile @@ -0,0 +1,54 @@ +# Dockerfile for standalone ZITADEL Login UI +FROM node:18-alpine AS base + +# Install dependencies only when needed +FROM base AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app + +# Prepare standalone and install dependencies +COPY prepare-standalone.sh package*.json ./ +COPY *.standalone.* ./ +RUN ./prepare-standalone.sh + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Prepare standalone configs +RUN ./prepare-standalone.sh --no-install + +# Build application +ENV NEXT_TELEMETRY_DISABLED 1 +RUN npm run build:standalone + +# Production image, copy all the files and run next +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV production +ENV NEXT_TELEMETRY_DISABLED 1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public + +# Set the correct permission for prerender cache +RUN mkdir .next +RUN chown nextjs:nodejs .next + +# Automatically leverage output traces to reduce image size +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT 3000 +ENV HOSTNAME "0.0.0.0" + +CMD ["node", "server.js"] diff --git a/login/apps/login/README.standalone.md b/login/apps/login/README.standalone.md new file mode 100644 index 0000000000..ce57de989a --- /dev/null +++ b/login/apps/login/README.standalone.md @@ -0,0 +1,81 @@ +# ZITADEL Login UI - Standalone + +This is the standalone version of the ZITADEL Login UI, a Next.js application that provides the authentication interface for ZITADEL. + +## Quick Start + +### Prerequisites + +- Node.js 18+ +- npm or pnpm + +### Setup + +1. **Prepare the standalone environment:** + ```bash + ./prepare-standalone.sh + ``` + +2. **Start development server:** + ```bash + npm run dev + ``` + +3. **Build for production:** + ```bash + npm run build:standalone + npm run start + ``` + +## Development + +### Available Scripts + +- `npm run dev` - Start development server with Turbopack +- `npm run build` - Build for production +- `npm run build:standalone` - Build standalone version with custom base path +- `npm run start` - Start production server +- `npm run test:unit` - Run unit tests +- `npm run lint` - Run linting +- `npm run lint:fix` - Fix linting issues + +### Environment Variables + +Create a `.env.local` file with your ZITADEL configuration: + +```env +ZITADEL_API_URL=https://your-zitadel-instance.com +# Add other required environment variables +``` + +## Differences from Monorepo Version + +This standalone version includes: + +- Self-contained configuration files +- Published versions of `@zitadel/client` and `@zitadel/proto` packages +- Standalone build scripts +- Independent dependency management + +## Contributing + +When contributing to this standalone version: + +1. Make changes in the main monorepo first +2. Test changes in the monorepo environment +3. Update the standalone version via subtree push +4. Test the standalone version independently + +## Subtree Sync + +This repository is maintained as a Git subtree of the main ZITADEL repository. + +To sync changes from the main repo: +```bash +# In the main ZITADEL repo +git subtree push --prefix=login/apps/login origin typescript-login-standalone +``` + +## License + +See the main ZITADEL repository for license information. diff --git a/login/apps/login/package.standalone.json b/login/apps/login/package.standalone.json new file mode 100644 index 0000000000..2d2f7a7718 --- /dev/null +++ b/login/apps/login/package.standalone.json @@ -0,0 +1,66 @@ +{ + "name": "@zitadel/login", + "private": true, + "type": "module", + "scripts": { + "dev": "next dev --turbopack", + "test:unit": "vitest", + "test:unit:watch": "vitest --watch", + "lint": "next lint && prettier --check .", + "lint:fix": "prettier --write .", + "build": "next build", + "build:standalone": "NEXT_PUBLIC_BASE_PATH=/ui/v2/login NEXT_OUTPUT_MODE=standalone next build", + "start": "npm run build && next start", + "start:built": "next start", + "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next" + }, + "dependencies": { + "@headlessui/react": "^2.1.9", + "@heroicons/react": "2.1.3", + "@tailwindcss/forms": "0.5.7", + "@vercel/analytics": "^1.2.2", + "@zitadel/client": "^1.0.0", + "@zitadel/proto": "^1.0.0", + "clsx": "1.2.1", + "copy-to-clipboard": "^3.3.3", + "deepmerge": "^4.3.1", + "lucide-react": "0.469.0", + "moment": "^2.29.4", + "next": "15.4.0-canary.86", + "next-intl": "^3.25.1", + "next-themes": "^0.2.1", + "nice-grpc": "2.0.1", + "qrcode.react": "^3.1.0", + "react": "19.1.0", + "react-dom": "19.1.0", + "react-hook-form": "7.39.5", + "tinycolor2": "1.4.2", + "uuid": "^11.1.0" + }, + "devDependencies": { + "@bufbuild/buf": "^1.53.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.0", + "@types/ms": "2.1.0", + "@types/node": "^22.14.1", + "@types/react": "19.1.2", + "@types/react-dom": "19.1.2", + "@types/tinycolor2": "1.4.3", + "@types/uuid": "^10.0.0", + "@vercel/git-hooks": "1.0.0", + "eslint": "8.57.1", + "eslint-config-next": "15.4.0-canary.86", + "prettier": "^3.5.3", + "autoprefixer": "10.4.21", + "grpc-tools": "1.13.0", + "jsdom": "^26.1.0", + "make-dir-cli": "4.0.0", + "postcss": "8.5.3", + "prettier-plugin-tailwindcss": "0.6.11", + "sass": "^1.87.0", + "tailwindcss": "3.4.14", + "ts-proto": "^2.7.0", + "typescript": "^5.8.3", + "vitest": "^3.1.2" + } +} diff --git a/login/apps/login/prepare-standalone.sh b/login/apps/login/prepare-standalone.sh new file mode 100755 index 0000000000..1be7bf28b7 --- /dev/null +++ b/login/apps/login/prepare-standalone.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Script to prepare standalone version of the login app +set -e + +echo "Preparing standalone version..." + +# Copy standalone configs +cp package.standalone.json package.json +cp tsconfig.standalone.json tsconfig.json +cp .eslintrc.standalone.cjs .eslintrc.cjs +cp prettier.config.standalone.mjs prettier.config.mjs +cp tailwind.config.standalone.mjs tailwind.config.mjs + +# Install dependencies unless --no-install is passed +if [ "$1" != "--no-install" ]; then + echo "Installing dependencies..." + npm install +fi + +echo "Standalone version prepared successfully!" +if [ "$1" != "--no-install" ]; then + echo "You can now run:" + echo " npm run dev - Start development server" + echo " npm run build - Build for production" + echo " npm run start - Start production server" +fi diff --git a/login/apps/login/prettier.config.standalone.mjs b/login/apps/login/prettier.config.standalone.mjs new file mode 100644 index 0000000000..d732c57e24 --- /dev/null +++ b/login/apps/login/prettier.config.standalone.mjs @@ -0,0 +1,8 @@ +export default { + semi: true, + trailingComma: "all", + singleQuote: false, + printWidth: 80, + tabWidth: 2, + plugins: ["prettier-plugin-tailwindcss"], +}; diff --git a/login/apps/login/tailwind.config.standalone.mjs b/login/apps/login/tailwind.config.standalone.mjs new file mode 100644 index 0000000000..3096a1d30e --- /dev/null +++ b/login/apps/login/tailwind.config.standalone.mjs @@ -0,0 +1,17 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", + "./src/components/**/*.{js,ts,jsx,tsx,mdx}", + "./src/app/**/*.{js,ts,jsx,tsx,mdx}", + ], + theme: { + extend: { + colors: { + background: "var(--background)", + foreground: "var(--foreground)", + }, + }, + }, + plugins: [require("@tailwindcss/forms")], +}; diff --git a/login/apps/login/tsconfig.standalone.json b/login/apps/login/tsconfig.standalone.json new file mode 100644 index 0000000000..e59724b283 --- /dev/null +++ b/login/apps/login/tsconfig.standalone.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +}