Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
root = true

[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.{py,java,gradle}]
indent_size = 4

[*.md]
trim_trailing_whitespace = false
14 changes: 14 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
groups:
github-actions:
patterns: ["*"]

- package-ecosystem: docker-compose
directory: /keycloak
schedule:
interval: monthly
31 changes: 31 additions & 0 deletions .github/workflows/_gradle-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: _gradle-ci

on:
workflow_call:
inputs:
working-directory:
type: string
required: true

permissions:
contents: read

jobs:
build:
name: Gradle build
runs-on: ubuntu-latest
timeout-minutes: 20
defaults:
run:
working-directory: ${{ inputs.working-directory }}
steps:
- uses: actions/checkout@v7

- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: "21"

- uses: gradle/actions/setup-gradle@v6

- run: ./gradlew build
41 changes: 41 additions & 0 deletions .github/workflows/_node-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: _node-ci

on:
workflow_call:
inputs:
working-directory:
type: string
required: true

permissions:
contents: read

jobs:
ci:
name: Build and check
runs-on: ubuntu-latest
timeout-minutes: 20
defaults:
run:
working-directory: ${{ inputs.working-directory }}
env:
NX_DAEMON: "false"
NX_NO_CLOUD: "true"
steps:
- uses: actions/checkout@v7

- uses: pnpm/action-setup@v6
with:
package_json_file: ${{ inputs.working-directory }}/package.json

- uses: actions/setup-node@v7
with:
node-version-file: ${{ inputs.working-directory }}/.nvmrc
cache: pnpm
cache-dependency-path: ${{ inputs.working-directory }}/pnpm-lock.yaml

- run: pnpm install --frozen-lockfile
- run: pnpm run --if-present typecheck
- run: pnpm run --if-present lint
- run: pnpm run --if-present test
- run: pnpm run build
63 changes: 63 additions & 0 deletions .github/workflows/_vercel-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: _vercel-deploy

on:
workflow_call:
inputs:
working-directory:
type: string
required: true
secrets:
VERCEL_ORG_ID:
required: false
VERCEL_PROJECT_ID:
required: false
VERCEL_TOKEN:
required: false

permissions:
contents: read

jobs:
deploy:
name: Deploy to Vercel
if: >-
github.repository == 'p2-inc/examples' &&
github.actor != 'dependabot[bot]' &&
(github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
runs-on: ubuntu-latest
timeout-minutes: 20
defaults:
run:
working-directory: ${{ inputs.working-directory }}
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
DEPLOY_TARGET: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/main') && 'production' || 'preview' }}
steps:
- uses: actions/checkout@v7

- uses: pnpm/action-setup@v6
with:
package_json_file: ${{ inputs.working-directory }}/package.json

- uses: actions/setup-node@v7
with:
node-version-file: ${{ inputs.working-directory }}/.nvmrc
cache: pnpm
cache-dependency-path: ${{ inputs.working-directory }}/pnpm-lock.yaml

- run: pnpm install --frozen-lockfile

- run: npm install --global vercel@59

- name: Pull Vercel project settings
run: vercel pull --yes --environment="$DEPLOY_TARGET" --token="$VERCEL_TOKEN"

- name: Build
run: vercel build ${{ env.DEPLOY_TARGET == 'production' && '--prod' || '' }} --token="$VERCEL_TOKEN"

- name: Deploy
run: |
url=$(vercel deploy --prebuilt ${{ env.DEPLOY_TARGET == 'production' && '--prod' || '' }} --token="$VERCEL_TOKEN")
echo "Deployed to Vercel ($DEPLOY_TARGET): $url" >> "$GITHUB_STEP_SUMMARY"
97 changes: 26 additions & 71 deletions .github/workflows/angular.yml
Original file line number Diff line number Diff line change
@@ -1,82 +1,37 @@
name: Deploy Angular Example
name: Angular (angular-oauth2-oidc)

env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_ANGULAR_PROJECT_ID }}
VERCEL_TOKEN: ${{ secrets.VERCEL_DEPLOYMENT_TOKEN }}
on:
push:
branches:
- main
branches: [main]
paths:
- "frameworks/angular/**"
- ".github/workflows/angular.yml"
pull_request:
branches:
- main
branches: [main]
paths:
- "frameworks/angular/**"
- ".github/workflows/angular.yml"
workflow_dispatch:

jobs:
deploy-preview:
if: github.event_name == 'pull_request' && github.repository == 'p2-inc/examples'
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v7

- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: "20"

- name: Install dependencies
run: npm install
working-directory: ./frameworks/angular

- name: Install Vercel CLI
run: npm install -g vercel@latest

- name: Pull Vercel Environment Information
run: vercel pull --yes --environment=preview --token=$VERCEL_TOKEN
working-directory: ./frameworks/angular

- name: Build Project Artifacts
run: vercel build --token=$VERCEL_TOKEN
working-directory: ./frameworks/angular

- name: Deploy Project Artifacts to Vercel
run: vercel deploy --prebuilt --token=$VERCEL_TOKEN
working-directory: ./frameworks/angular
permissions:
contents: read

deploy-prod:
if: github.event_name == 'push' && github.repository == 'p2-inc/examples'
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

steps:
- name: Checkout code
uses: actions/checkout@v7

- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: "20"

- name: Install dependencies
run: npm install
working-directory: ./frameworks/angular

- name: Install Vercel CLI
run: npm install -g vercel@latest

- name: Pull Vercel Environment Information
run: vercel pull --yes --environment=production --token=$VERCEL_TOKEN
working-directory: ./frameworks/angular

- name: Build Project Artifacts
run: vercel build --prod --token=$VERCEL_TOKEN
working-directory: ./frameworks/angular

- name: Deploy Project Artifacts to Vercel
run: vercel deploy --prebuilt --prod --token=$VERCEL_TOKEN
working-directory: ./frameworks/angular
jobs:
ci:
uses: ./.github/workflows/_node-ci.yml
with:
working-directory: frameworks/angular

deploy:
needs: ci
uses: ./.github/workflows/_vercel-deploy.yml
with:
working-directory: frameworks/angular
secrets:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_ANGULAR_PROJECT_ID }}
VERCEL_TOKEN: ${{ secrets.VERCEL_DEPLOYMENT_TOKEN }}
25 changes: 21 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,38 @@
node_modules
.pnp
.pnp.js
.pnpm-store

# testing
coverage

# production
build
dist
.next
.nuxt
.output
.svelte-kit
.react-router
.angular
.nx/cache
.nx/workspace-data
*.tsbuildinfo
.vercel
__pycache__
*.py[cod]
.venv
*.sqlite3
.gradle

# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
.env.*.local
.env.production

npm-debug.log*
yarn-debug.log*
yarn-error.log*
yarn-error.log*
pnpm-debug.log*
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24
62 changes: 62 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Contributing

Examples are read, copied and pasted more than they are run, so they favour clarity over cleverness and follow the same conventions as Phase Two's own apps.

## Structure

- One standalone folder per example. Nothing is shared between examples at build time: each has its own lockfile, config and assets, so copying the folder is enough to run it.
- Keep folder paths stable. The tutorials on [phasetwo.io](https://phasetwo.io/blog) link to them.
- Each example has its own workflow in `.github/workflows`, which calls the shared `_node-ci.yml`, `_vercel-deploy.yml` or `_gradle-ci.yml`. Its `paths` filter lists the example folder and the workflow file itself.

## JavaScript and TypeScript examples

| Topic | Convention |
| --------------- | ----------------------------------------------------------------------------------------------------------- |
| Runtime | Node.js 24: `.nvmrc` and `"engines": { "node": "24.x" }` |
| Package manager | pnpm, pinned with `"packageManager"`. Only `pnpm-lock.yaml` is committed. |
| Language | TypeScript, `strict`, the framework's own tsconfig |
| Scripts | `dev`, `build`, `preview` or `start`, `typecheck`, `lint`, `format`, and `test` when there are tests |
| Styling | Tailwind CSS 4, configured in CSS (`@import "tailwindcss"` and an `@theme` block). No `tailwind.config.js`. |
| Lint and format | ESLint flat config from the framework's preset, Prettier with `prettier-plugin-tailwindcss` |
| Configuration | Public settings in env files; secrets only in the environment, never in git |

Environment variables:

- Vite SPAs commit a `.env` with the public settings of the hosted demo realm (`VITE_OIDC_ISSUER_URI`, `VITE_OIDC_CLIENT_ID`) and a `.env.local.sample` for the local Keycloak. Copy it to `.env.local`, which is ignored by git.
- Server-side examples commit only `.env.example`, pointing at the local Keycloak.

Local ports, so examples match the Keycloak clients in [`keycloak/realms/p2examples.json`](./keycloak/realms/p2examples.json):

| Port | Examples |
| ----------- | -------------------------------------------------------------------------- |
| 3000 | React, Vue, Nuxt, Next.js, React Router (Remix), SvelteKit |
| 4200 / 4201 | Angular, Spring Boot's Angular client, multitenant zoo / aquarium |
| 8000 | Django |
| 8080 | the local Keycloak, or the Spring Boot API (with its own Keycloak on 8888) |
| 8081 | SAML service provider |

## Authentication

- Use the authorization code flow with PKCE. Never ship a client secret in browser code.
- Log out through Keycloak so the SSO session ends, not only the local session.
- Keep tokens out of the browser in server-side examples; show decoded claims instead.
- Let the library refresh tokens; no `setInterval` refresh loops.

## Look and feel

All examples share the same page, so they are easy to compare and one smoke test covers them all:

- the Phase Two background (`home-bg.webp`, `home-bg-mobile.webp`) and logo, and the `p2blue`, `p2gray`, `p2grad` and `p2dark` colours;
- a header linking to phasetwo.io and to the example's own folder on GitHub;
- the status line "Your current status is:" followed by "Not authenticated." or "Authenticated", with "Log in" and "Log out" buttons;
- decoded "Access token (decoded)" and "ID token (decoded)" panels;
- the Docs, Github, Blog and Contact footer.

Icons are small inline SVG components in each example, so they inherit the text colour. No icon library.

## Before opening a pull request

- `pnpm install --frozen-lockfile`, `pnpm typecheck`, `pnpm lint`, `pnpm test` and `pnpm build` pass on Node.js 24 (or `./gradlew build`, or `python manage.py test`).
- Logging in and out works against the local Keycloak. For the JavaScript examples, run [`tools/e2e-smoke`](./tools/e2e-smoke).
- No secrets are committed.
- The pull request description has a **Docs drift** section listing the phasetwo.io tutorials and docs pages whose code snippets no longer match the example.
Loading
Loading