✨ stou theme for keycloak
This commit is contained in:
commit
931dac3449
16
.editorconfig
Normal file
16
.editorconfig
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Editor configuration, see https://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.ts]
|
||||||
|
quote_type = single
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
max_line_length = off
|
||||||
|
trim_trailing_whitespace = false
|
57
.gitignore
vendored
Normal file
57
.gitignore
vendored
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# Compiled output
|
||||||
|
/dist
|
||||||
|
/tmp
|
||||||
|
/out-tsc
|
||||||
|
/bazel-out
|
||||||
|
|
||||||
|
# Node
|
||||||
|
/node_modules
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
.idea/
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode/*
|
||||||
|
.history/*
|
||||||
|
|
||||||
|
# Miscellaneous
|
||||||
|
/.angular/cache
|
||||||
|
/.nx/cache
|
||||||
|
/.nx/workspace-data
|
||||||
|
.sass-cache/
|
||||||
|
/connect.lock
|
||||||
|
/coverage
|
||||||
|
/libpeerconnection.log
|
||||||
|
testem.log
|
||||||
|
/typings
|
||||||
|
|
||||||
|
# System files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# VS Code devcontainers
|
||||||
|
.devcontainer
|
||||||
|
/.yarn
|
||||||
|
/.yarnrc.yml
|
||||||
|
|
||||||
|
/dist_keycloak
|
||||||
|
/build
|
||||||
|
/storybook-static
|
||||||
|
|
||||||
|
/stories/assets/fonts/
|
||||||
|
/build_storybook/
|
||||||
|
/storybook-static/
|
||||||
|
|
||||||
|
*storybook.log
|
||||||
|
|
||||||
|
.nx
|
4
.prettierignore
Normal file
4
.prettierignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
node_modules/
|
||||||
|
/dist/
|
||||||
|
/dist_keycloak/
|
||||||
|
/public/keycloak-dev-resources/
|
39
.storybook/main.ts
Normal file
39
.storybook/main.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import type { StorybookConfig } from '@storybook/angular';
|
||||||
|
import { StorybookConfigVite } from '@storybook/builder-vite';
|
||||||
|
import { UserConfig } from 'vite';
|
||||||
|
|
||||||
|
const config: StorybookConfig & StorybookConfigVite = {
|
||||||
|
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||||
|
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
|
||||||
|
framework: {
|
||||||
|
name: '@storybook/angular',
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
core: {
|
||||||
|
builder: {
|
||||||
|
name: '@storybook/builder-vite',
|
||||||
|
options: {
|
||||||
|
viteConfigPath: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
staticDirs: ['../public'],
|
||||||
|
async viteFinal(config: UserConfig, { configType }) {
|
||||||
|
// Merge custom configuration into the default config
|
||||||
|
const { mergeConfig } = await import('vite');
|
||||||
|
const { default: angular } = await import('@analogjs/vite-plugin-angular');
|
||||||
|
|
||||||
|
return mergeConfig(config, {
|
||||||
|
// Add dependencies to pre-optimization
|
||||||
|
optimizeDeps: {
|
||||||
|
include: ['@storybook/angular', '@storybook/angular/dist', '@angular/compiler', '@storybook/blocks', 'tslib'],
|
||||||
|
},
|
||||||
|
define: {
|
||||||
|
'process.env': {},
|
||||||
|
'process.env.NODE_ENV': JSON.stringify(configType === 'PRODUCTION' ? 'production' : 'development'),
|
||||||
|
},
|
||||||
|
plugins: [angular({ jit: true, tsconfig: './.storybook/tsconfig.json' })],
|
||||||
|
} satisfies UserConfig);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export default config;
|
14
.storybook/preview.ts
Normal file
14
.storybook/preview.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import type { Preview } from '@storybook/angular';
|
||||||
|
|
||||||
|
const preview: Preview = {
|
||||||
|
parameters: {
|
||||||
|
controls: {
|
||||||
|
matchers: {
|
||||||
|
color: /(background|color)$/i,
|
||||||
|
date: /Date$/i,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default preview;
|
10
.storybook/tsconfig.doc.json
Normal file
10
.storybook/tsconfig.doc.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// This tsconfig is used by Compodoc to generate the documentation for the project.
|
||||||
|
// If Compodoc is not used, this file can be deleted.
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
// Exclude all files that are not needed for documentation generation.
|
||||||
|
"exclude": ["../src/test.ts", "../src/**/*.spec.ts", "../src/**/*.stories.ts"],
|
||||||
|
// Please make sure to include all files from which Compodoc should generate documentation.
|
||||||
|
"include": ["../src/**/*"],
|
||||||
|
"files": ["./typings.d.ts"]
|
||||||
|
}
|
21
.storybook/tsconfig.json
Normal file
21
.storybook/tsconfig.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.app.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": [
|
||||||
|
"node",
|
||||||
|
],
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"resolveJsonModule": true
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"../src/test.ts",
|
||||||
|
"../src/**/*.spec.ts"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"../src/**/*.stories.*",
|
||||||
|
"./preview.ts"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"./typings.d.ts"
|
||||||
|
]
|
||||||
|
}
|
4
.storybook/typings.d.ts
vendored
Normal file
4
.storybook/typings.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
declare module '*.md' {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 GitHub user u/luca-peruzzo
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
51
README.md
Normal file
51
README.md
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<p align="center">
|
||||||
|
<i>🚀 <a href="https://keycloakify.dev">Angular + Vite Keycloakify</a> v11 starter 🚀</i>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
This starter is based on Vite and Angular. There is also [a Webpack based starter](https://github.com/keycloakify/keycloakify-starter-angular).
|
||||||
|
|
||||||
|
# Quick start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/keycloakify/keycloakify-starter-angular-vite
|
||||||
|
cd keycloakify-starter-angular-vite
|
||||||
|
yarn install # Or use an other package manager, just be sure to delete the yarn.lock if you use another package manager.
|
||||||
|
```
|
||||||
|
|
||||||
|
# Testing the theme locally
|
||||||
|
|
||||||
|
[Documentation](https://docs.keycloakify.dev/testing-your-theme)
|
||||||
|
|
||||||
|
# How to customize the theme
|
||||||
|
|
||||||
|
[Documentation](https://docs.keycloakify.dev/customization-strategies)
|
||||||
|
|
||||||
|
# Building the theme
|
||||||
|
|
||||||
|
You need to have [Maven](https://maven.apache.org/) installed to build the theme (Maven >= 3.1.1, Java >= 7).
|
||||||
|
The `mvn` command must be in the $PATH.
|
||||||
|
|
||||||
|
- On macOS: `brew install maven`
|
||||||
|
- On Debian/Ubuntu: `sudo apt-get install maven`
|
||||||
|
- On Windows: `choco install openjdk` and `choco install maven` (Or download from [here](https://maven.apache.org/download.cgi))
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build-keycloak-theme
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that by default Keycloakify generates multiple .jar files for different versions of Keycloak.
|
||||||
|
You can customize this behavior, see documentation [here](https://docs.keycloakify.dev/targeting-specific-keycloak-versions).
|
||||||
|
|
||||||
|
# Initializing the account theme
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx keycloakify initialize-account-theme
|
||||||
|
```
|
||||||
|
|
||||||
|
# Initializing the email theme
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx keycloakify initialize-email-theme
|
||||||
|
```
|
61
angular.json
Normal file
61
angular.json
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"keycloakify-starter-angular-vite": {
|
||||||
|
"projectType": "application",
|
||||||
|
"root": ".",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"prefix": "kc",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@analogjs/platform:vite",
|
||||||
|
"options": {
|
||||||
|
"configFile": "vite.config.ts",
|
||||||
|
"main": "src/main.ts",
|
||||||
|
"outputPath": "dist",
|
||||||
|
"tsConfig": "tsconfig.app.json"
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production",
|
||||||
|
"configurations": {
|
||||||
|
"development": {
|
||||||
|
"mode": "development"
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"sourcemap": false,
|
||||||
|
"mode": "production"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"builder": "@analogjs/platform:vite-dev-server",
|
||||||
|
"defaultConfiguration": "development",
|
||||||
|
"options": {
|
||||||
|
"buildTarget": "keycloakify-starter-angular-vite:build",
|
||||||
|
"port": 5173
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"development": {
|
||||||
|
"buildTarget": "keycloakify-starter-angular-vite:build:development",
|
||||||
|
"hmr": true
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"buildTarget": "keycloakify-starter-angular-vite:build:production"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"builder": "@angular-eslint/builder:lint",
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cli": {
|
||||||
|
"analytics": false,
|
||||||
|
"schematicCollections": ["angular-eslint"]
|
||||||
|
}
|
||||||
|
}
|
78
eslint.config.js
Normal file
78
eslint.config.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// @ts-check
|
||||||
|
import eslint from '@eslint/js';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
import angular from 'angular-eslint';
|
||||||
|
import prettier from 'eslint-plugin-prettier';
|
||||||
|
import storybook from 'eslint-plugin-storybook';
|
||||||
|
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{
|
||||||
|
files: ['*.stories.@(ts|tsx|js|jsx|mjs|cjs)', '*.story.@(ts|tsx|js|jsx|mjs|cjs)'],
|
||||||
|
plugins: {
|
||||||
|
prettier,
|
||||||
|
storybook
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'import/no-anonymous-default-export': 'off',
|
||||||
|
'prettier/prettier': ['error', {}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.ts'],
|
||||||
|
plugins: {
|
||||||
|
prettier,
|
||||||
|
},
|
||||||
|
extends: [eslint.configs.recommended, ...tseslint.configs.recommended, ...tseslint.configs.stylistic, ...angular.configs.tsRecommended],
|
||||||
|
processor: angular.processInlineTemplates,
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/consistent-type-definitions': 'off',
|
||||||
|
'@typescript-eslint/no-inferrable-types': 'off',
|
||||||
|
'@typescript-eslint/no-namespace': 'off',
|
||||||
|
'@typescript-eslint/no-empty-object-type': 'off',
|
||||||
|
'@angular-eslint/directive-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'attribute',
|
||||||
|
prefix: 'kc',
|
||||||
|
style: 'camelCase',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@angular-eslint/component-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
prefix: 'kc',
|
||||||
|
style: 'kebab-case',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'prettier/prettier': [
|
||||||
|
'error',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
usePrettierrc: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.html'],
|
||||||
|
plugins: {
|
||||||
|
prettier,
|
||||||
|
},
|
||||||
|
extends: [...angular.configs.templateRecommended, ...angular.configs.templateAccessibility],
|
||||||
|
rules: {
|
||||||
|
'@angular-eslint/template/interactive-supports-focus': 'off',
|
||||||
|
'@angular-eslint/template/click-events-have-key-events': 'off',
|
||||||
|
'@angular-eslint/template/label-has-associated-control': 'off',
|
||||||
|
'@angular-eslint/template/no-autofocus': 'off',
|
||||||
|
'prettier/prettier': [
|
||||||
|
'error',
|
||||||
|
{ parser: 'angular' },
|
||||||
|
{
|
||||||
|
usePrettierrc: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
23
index.html
Normal file
23
index.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<base href="/" />
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
href="/favicon-32x32.png"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<kc-root></kc-root>
|
||||||
|
<script
|
||||||
|
type="module"
|
||||||
|
src="/src/main.ts"
|
||||||
|
></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
83
package.json
Normal file
83
package.json
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
{
|
||||||
|
"name": "keycloakify-stou",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"description": "Angular + Vite Starter for Keycloakify 11",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git://github.com/keycloakify/keycloakify-starter-angular-vite.git"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"ng": "ng",
|
||||||
|
"dev": "ng serve",
|
||||||
|
"start": "yarn dev",
|
||||||
|
"build": "ng build",
|
||||||
|
"build-keycloak-theme": "npm run build && keycloakify build",
|
||||||
|
"test": "ng test",
|
||||||
|
"lint": "ng lint --fix",
|
||||||
|
"storybook": "storybook dev --port 4400"
|
||||||
|
},
|
||||||
|
"author": "u/luca-peruzzo, u/kathari00, u/garronej",
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": [
|
||||||
|
"angular",
|
||||||
|
"keycloakify",
|
||||||
|
"keycloak",
|
||||||
|
"vite",
|
||||||
|
"analog"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/animations": "^19.1.2",
|
||||||
|
"@angular/common": "^19.1.2",
|
||||||
|
"@angular/compiler": "^19.1.2",
|
||||||
|
"@angular/core": "^19.1.2",
|
||||||
|
"@angular/forms": "^19.1.2",
|
||||||
|
"@angular/platform-browser": "^19.1.2",
|
||||||
|
"@angular/platform-browser-dynamic": "^19.1.2",
|
||||||
|
"@keycloakify/angular": "^0.2.17",
|
||||||
|
"front-matter": "^4.0.2",
|
||||||
|
"keycloakify": "^11.8.9",
|
||||||
|
"prismjs": "^1.29.0",
|
||||||
|
"rxjs": "~7.8.1",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@analogjs/platform": "^1.12.1",
|
||||||
|
"@analogjs/vite-plugin-angular": "^1.12.1",
|
||||||
|
"@analogjs/vitest-angular": "^1.12.1",
|
||||||
|
"@angular-devkit/architect": "^0.1901.3",
|
||||||
|
"@angular-devkit/build-angular": "^19.1.3",
|
||||||
|
"@angular/build": "^19.1.3",
|
||||||
|
"@angular/cli": "^19.1.3",
|
||||||
|
"@angular/compiler-cli": "^19.1.2",
|
||||||
|
"@nx/angular": "^20.3.2",
|
||||||
|
"@nx/devkit": "^20.3.2",
|
||||||
|
"@nx/vite": "^20.3.2",
|
||||||
|
"@storybook/addon-essentials": "^8.5.0",
|
||||||
|
"@storybook/addon-interactions": "^8.5.0",
|
||||||
|
"@storybook/angular": "^8.5.0",
|
||||||
|
"@storybook/blocks": "^8.5.0",
|
||||||
|
"@storybook/builder-vite": "^8.5.0",
|
||||||
|
"@storybook/test": "^8.5.0",
|
||||||
|
"@types/node": "^22.10.7",
|
||||||
|
"@typescript-eslint/types": "^8.21.0",
|
||||||
|
"@typescript-eslint/utils": "^8.21.0",
|
||||||
|
"angular-eslint": "^19.0.2",
|
||||||
|
"eslint": "^9.18.0",
|
||||||
|
"eslint-plugin-prettier": "^5.2.3",
|
||||||
|
"eslint-plugin-storybook": "^0.11.2",
|
||||||
|
"jsdom": "^26.0.0",
|
||||||
|
"npm-check-updates": "^17.1.14",
|
||||||
|
"prettier": "^3.4.2",
|
||||||
|
"storybook": "^8.5.0",
|
||||||
|
"typescript": "~5.6.3",
|
||||||
|
"typescript-eslint": "^8.21.0",
|
||||||
|
"vite": "^5.4.12",
|
||||||
|
"vite-tsconfig-paths": "^5.1.4",
|
||||||
|
"vitest": "^3.0.3",
|
||||||
|
"zone.js": "~0.15.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
}
|
||||||
|
}
|
25
prettier.config.js
Normal file
25
prettier.config.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* @see https://prettier.io/docs/en/configuration.html
|
||||||
|
* @type {import("prettier").Config}
|
||||||
|
*/
|
||||||
|
const config = {
|
||||||
|
printWidth: 120,
|
||||||
|
singleQuote: true,
|
||||||
|
trailingComma: "all",
|
||||||
|
tabWidth: 2,
|
||||||
|
useTabs: false,
|
||||||
|
semi: true,
|
||||||
|
bracketSpacing: true,
|
||||||
|
arrowParens: "always",
|
||||||
|
singleAttributePerLine: true,
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: "*.html",
|
||||||
|
options: {
|
||||||
|
parser: "angular"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
0
public/.gitkeep
Normal file
0
public/.gitkeep
Normal file
BIN
public/favicon-32x32.png
Normal file
BIN
public/favicon-32x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
6
src/app.config.ts
Normal file
6
src/app.config.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import type { ApplicationConfig } from '@angular/core';
|
||||||
|
import { provideExperimentalZonelessChangeDetection } from '@angular/core';
|
||||||
|
|
||||||
|
export const appConfig: ApplicationConfig = {
|
||||||
|
providers: [provideExperimentalZonelessChangeDetection()],
|
||||||
|
};
|
BIN
src/assets/font/Sarabun-Light.ttf
Normal file
BIN
src/assets/font/Sarabun-Light.ttf
Normal file
Binary file not shown.
BIN
src/assets/image/logo.jpg
Normal file
BIN
src/assets/image/logo.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 194 KiB |
86
src/kc.gen.ts
Normal file
86
src/kc.gen.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// This file is auto-generated by keycloakify. Do not edit it manually.
|
||||||
|
// Hash: 60fd6e41f85a18c23f8cb579b9476ceff3620dda085139def3e4c0137b4d47f1
|
||||||
|
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
|
||||||
|
import type { ComponentRef, EnvironmentProviders, Type } from '@angular/core';
|
||||||
|
|
||||||
|
export type ThemeName = 'keycloakify-stou';
|
||||||
|
|
||||||
|
export const themeNames: ThemeName[] = ['keycloakify-stou'];
|
||||||
|
|
||||||
|
export type KcEnvName = never;
|
||||||
|
|
||||||
|
export const kcEnvNames: KcEnvName[] = [];
|
||||||
|
|
||||||
|
export const kcEnvDefaults: Record<KcEnvName, string> = {};
|
||||||
|
|
||||||
|
export type KcContext = import('./login/KcContext').KcContext;
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
kcContext?: KcContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ApplicationRefLike = {
|
||||||
|
components: ComponentRef<any>[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function bootstrapKcApplication(params: {
|
||||||
|
kcContext: KcContext;
|
||||||
|
bootstrapApplication: (params: {
|
||||||
|
KcRootComponent: Type<unknown>;
|
||||||
|
kcProvider: EnvironmentProviders;
|
||||||
|
}) => Promise<ApplicationRefLike>;
|
||||||
|
}) {
|
||||||
|
const { kcContext, bootstrapApplication } = params;
|
||||||
|
|
||||||
|
switch (kcContext.themeType) {
|
||||||
|
case 'login':
|
||||||
|
{
|
||||||
|
const [
|
||||||
|
{ provideKeycloakifyAngular },
|
||||||
|
{ getI18n },
|
||||||
|
{
|
||||||
|
PageComponent,
|
||||||
|
TemplateComponent,
|
||||||
|
doUseDefaultCss,
|
||||||
|
classes,
|
||||||
|
UserProfileFormFieldsComponent,
|
||||||
|
doMakeUserConfirmPassword,
|
||||||
|
},
|
||||||
|
] = await Promise.all([
|
||||||
|
import('@keycloakify/angular/login/providers/keycloakify-angular'),
|
||||||
|
import('./login/i18n'),
|
||||||
|
import('./login/KcPage').then(({ getKcPage }) => getKcPage(kcContext.pageId)),
|
||||||
|
] as const);
|
||||||
|
|
||||||
|
const appRef = await bootstrapApplication({
|
||||||
|
KcRootComponent: TemplateComponent,
|
||||||
|
kcProvider: provideKeycloakifyAngular({
|
||||||
|
kcContext,
|
||||||
|
classes,
|
||||||
|
getI18n,
|
||||||
|
doUseDefaultCss,
|
||||||
|
doMakeUserConfirmPassword,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
appRef.components.forEach((componentRef) => {
|
||||||
|
// page must be defined first
|
||||||
|
if ('page' in componentRef.instance) {
|
||||||
|
componentRef.setInput('page', PageComponent);
|
||||||
|
}
|
||||||
|
if ('userProfileFormFields' in componentRef.instance) {
|
||||||
|
componentRef.setInput('userProfileFormFields', UserProfileFormFieldsComponent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
14
src/login/KcContext.ts
Normal file
14
src/login/KcContext.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import type { ExtendKcContext } from 'keycloakify/login';
|
||||||
|
import type { KcEnvName, ThemeName } from '../kc.gen';
|
||||||
|
|
||||||
|
export type KcContextExtension = {
|
||||||
|
themeName: ThemeName;
|
||||||
|
properties: Record<KcEnvName, string> & {};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type KcContextExtensionPerPage = {
|
||||||
|
// Here you can declare additional properties on the KcContext
|
||||||
|
// See: https://docs.keycloakify.dev/faq-and-help/some-values-you-need-are-missing-from-in-kccontext
|
||||||
|
};
|
||||||
|
|
||||||
|
export type KcContext = ExtendKcContext<KcContextExtension, KcContextExtensionPerPage>;
|
24
src/login/KcPage.ts
Normal file
24
src/login/KcPage.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import "./main.css";
|
||||||
|
import { getDefaultPageComponent, type KcPage } from '@keycloakify/angular/login';
|
||||||
|
import { UserProfileFormFieldsComponent } from '@keycloakify/angular/login/components/user-profile-form-fields';
|
||||||
|
import { TemplateComponent } from '@keycloakify/angular/login/template';
|
||||||
|
import type { ClassKey } from 'keycloakify/login';
|
||||||
|
import type { KcContext } from './KcContext';
|
||||||
|
|
||||||
|
export const classes = {} satisfies Partial<Record<ClassKey, string>>;
|
||||||
|
export const doUseDefaultCss = true;
|
||||||
|
export const doMakeUserConfirmPassword = true;
|
||||||
|
|
||||||
|
export async function getKcPage(pageId: KcContext['pageId']): Promise<KcPage> {
|
||||||
|
switch (pageId) {
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
PageComponent: await getDefaultPageComponent(pageId),
|
||||||
|
TemplateComponent,
|
||||||
|
UserProfileFormFieldsComponent,
|
||||||
|
doMakeUserConfirmPassword,
|
||||||
|
doUseDefaultCss,
|
||||||
|
classes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
70
src/login/KcPageStory.ts
Normal file
70
src/login/KcPageStory.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/* eslint-disable @angular-eslint/component-class-suffix */
|
||||||
|
import { Component, inject, OnInit, Type } from '@angular/core';
|
||||||
|
import { provideKeycloakifyAngular } from '@keycloakify/angular/login/providers/keycloakify-angular';
|
||||||
|
import { TemplateComponent } from '@keycloakify/angular/login/template';
|
||||||
|
import { getKcPage } from './KcPage';
|
||||||
|
import { getI18n } from './i18n';
|
||||||
|
import { KC_LOGIN_CONTEXT } from '@keycloakify/angular/login/tokens/kc-context';
|
||||||
|
import { createGetKcContextMock } from 'keycloakify/login/KcContext';
|
||||||
|
import { kcEnvDefaults, themeNames } from '../kc.gen';
|
||||||
|
import type { KcContextExtension, KcContextExtensionPerPage } from './KcContext';
|
||||||
|
import { classes, doMakeUserConfirmPassword, doUseDefaultCss } from './KcPage';
|
||||||
|
const kcContextExtension: KcContextExtension = {
|
||||||
|
themeName: themeNames[0],
|
||||||
|
properties: {
|
||||||
|
...kcEnvDefaults,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const kcContextExtensionPerPage: KcContextExtensionPerPage = {};
|
||||||
|
|
||||||
|
export const { getKcContextMock } = createGetKcContextMock({
|
||||||
|
kcContextExtension,
|
||||||
|
kcContextExtensionPerPage,
|
||||||
|
overrides: {},
|
||||||
|
overridesPerPage: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
type StoryContextLike = {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
globals: Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const decorators = (_: unknown, context: StoryContextLike) => ({
|
||||||
|
applicationConfig: {
|
||||||
|
providers: [
|
||||||
|
provideKeycloakifyAngular({
|
||||||
|
doMakeUserConfirmPassword: doMakeUserConfirmPassword,
|
||||||
|
doUseDefaultCss: doUseDefaultCss,
|
||||||
|
classes: classes,
|
||||||
|
kcContext: getKcContextMock({
|
||||||
|
pageId: context.globals['pageId'],
|
||||||
|
overrides: context.globals['kcContext'],
|
||||||
|
}),
|
||||||
|
getI18n: getI18n,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'kc-page-story',
|
||||||
|
template: `@if (pageComponent) {
|
||||||
|
<kc-root
|
||||||
|
[page]="pageComponent"
|
||||||
|
[userProfileFormFields]="userProfileFormFieldsComponent"
|
||||||
|
></kc-root>
|
||||||
|
}`,
|
||||||
|
standalone: true,
|
||||||
|
imports: [TemplateComponent],
|
||||||
|
})
|
||||||
|
export class KcPageStory implements OnInit {
|
||||||
|
pageComponent: Type<unknown> | undefined;
|
||||||
|
kcContext = inject(KC_LOGIN_CONTEXT);
|
||||||
|
userProfileFormFieldsComponent: Type<unknown> | undefined;
|
||||||
|
ngOnInit() {
|
||||||
|
getKcPage(this.kcContext.pageId).then((kcPage) => {
|
||||||
|
this.pageComponent = kcPage.PageComponent;
|
||||||
|
this.userProfileFormFieldsComponent = kcPage.UserProfileFormFieldsComponent;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
10
src/login/i18n.ts
Normal file
10
src/login/i18n.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
import { i18nBuilder } from '@keycloakify/angular/login';
|
||||||
|
import type { ThemeName } from '../kc.gen';
|
||||||
|
|
||||||
|
/** @see: https://docs.keycloakify.dev/features/i18n */
|
||||||
|
const { getI18n, ofTypeI18n } = i18nBuilder.withThemeName<ThemeName>().build();
|
||||||
|
|
||||||
|
type I18n = typeof ofTypeI18n;
|
||||||
|
|
||||||
|
export { getI18n, type I18n };
|
159
src/login/main.css
Normal file
159
src/login/main.css
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
@font-face{
|
||||||
|
font-family: "Sarabun-Light";
|
||||||
|
src: url(../assets/font/Sarabun-Light.ttf);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-family);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-pf a {
|
||||||
|
color: #1e3a8a;
|
||||||
|
}
|
||||||
|
.login-pf a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: #1e3a8a;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
color: #1e3a8a;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.kcBodyClass {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
#kc-header {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
#kc-header-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 20px;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
background-image: url(../assets/image/logo.jpg); /* Replace with your logo file */
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: auto 150px; /* Maintain aspect ratio, height = 100px */
|
||||||
|
background-position: center top; /* Position logo above text */
|
||||||
|
padding-top: 160px; /* Adjust space so text is not covered */
|
||||||
|
letter-spacing: normal;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #333;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.kcLabelClass.pf-c-form__label.pf-c-form__label-text {
|
||||||
|
color: #333;
|
||||||
|
font-size: calc(var(--textFontSize) + 0.2em);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-pf-page .login-pf-header h1 {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: calc(var(--textFontSize) + 0.5em);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1#kc-page-title {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#kc-header-wrapper {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #333;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Login form styling */
|
||||||
|
/* .kcFormCardClass.card-pf {
|
||||||
|
background-color: white;
|
||||||
|
padding: 10px 20px 20px 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
width: 350px;
|
||||||
|
height: auto;
|
||||||
|
} */
|
||||||
|
|
||||||
|
div.kcFormGroupClass.form-group {
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.kcInputClass.pf-c-form-control {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 0;
|
||||||
|
font-size: var(--textFontSize);
|
||||||
|
height: auto;
|
||||||
|
margin: 0;
|
||||||
|
font-family: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
font-feature-settings: var(--font-feature-settings, normal);
|
||||||
|
color: #4b5563;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
transition: background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s;
|
||||||
|
appearance: none;
|
||||||
|
font-weight: normal;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
input.kcInputClass.pf-c-form-control:hover {
|
||||||
|
border-color: #3B82F6;
|
||||||
|
}
|
||||||
|
input.kcInputClass.pf-c-form-control:focus {
|
||||||
|
border-color: #3B82F6;
|
||||||
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5);
|
||||||
|
}
|
||||||
|
.p-inputtext {
|
||||||
|
font-size: var(--textFontSize);
|
||||||
|
padding: 0.25rem 0.25rem 0.25rem 0.35rem;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.kcInputGroup.pf-c-input-group {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
/* merge with input.kcInputClass.pf-c-form-control */
|
||||||
|
.kcFormPasswordVisibilityButtonClass {
|
||||||
|
position: absolute;
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
right: 0;
|
||||||
|
height: 100%;
|
||||||
|
border-color: rgba(230, 230, 230, 0.5);
|
||||||
|
}
|
||||||
|
button.kcFormPasswordVisibilityButtonClass.pf-c-button.pf-m-control {
|
||||||
|
--pf-c-button--BorderRadius: none;
|
||||||
|
--pf-c-button--disabled--BackgroundColor: none;
|
||||||
|
--pf-c-button--after--BorderWidth: none;
|
||||||
|
--pf-c-button--after--BorderColor: none;
|
||||||
|
/* color: transparent; */
|
||||||
|
background-color: transparent;
|
||||||
|
/* made it on top even the input is focused */
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-c-button.pf-m-primary {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #1e3a8a;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.pf-c-button.pf-m-primary:hover {
|
||||||
|
background-color: #3b5998;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* custom variable */
|
||||||
|
:root {
|
||||||
|
--textFontSize: 1em;
|
||||||
|
--font-family: 'Sarabun-Light', sans-serif;
|
||||||
|
--font-feature-settings: "cv02" "cv03" "cv04" "cv11";
|
||||||
|
}
|
306
src/login/pages/login/login.stories.ts
Normal file
306
src/login/pages/login/login.stories.ts
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
import { Meta, StoryObj } from '@storybook/angular';
|
||||||
|
import { decorators, KcPageStory } from '../../KcPageStory';
|
||||||
|
|
||||||
|
const meta: Meta<KcPageStory> = {
|
||||||
|
title: 'login/login.ftl',
|
||||||
|
component: KcPageStory,
|
||||||
|
decorators: decorators,
|
||||||
|
globals: {
|
||||||
|
pageId: 'login.ftl',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<KcPageStory>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
globals: {
|
||||||
|
kcContext: {
|
||||||
|
locale: {
|
||||||
|
currentLanguageTag: "th"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithInvalidCredential: Story = {
|
||||||
|
globals: {
|
||||||
|
kcContext: {
|
||||||
|
locale: {
|
||||||
|
currentLanguageTag: "th"
|
||||||
|
},
|
||||||
|
login: {
|
||||||
|
username: 'johndoe',
|
||||||
|
},
|
||||||
|
messagesPerField: {
|
||||||
|
existsError: (fieldName: string, ...otherFieldNames: string[]) => {
|
||||||
|
const fieldNames = [fieldName, ...otherFieldNames];
|
||||||
|
return fieldNames.includes('username') || fieldNames.includes('password');
|
||||||
|
},
|
||||||
|
get: (fieldName: string) => {
|
||||||
|
return fieldName === 'username' || fieldName === 'password' ? 'Invalid username or password.' : '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithoutRegistration: Story = {
|
||||||
|
globals: {
|
||||||
|
kcContext: {
|
||||||
|
locale: {
|
||||||
|
currentLanguageTag: "th"
|
||||||
|
},
|
||||||
|
realm: { registrationAllowed: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithoutRememberMe: Story = {
|
||||||
|
globals: {
|
||||||
|
kcContext: {
|
||||||
|
locale: {
|
||||||
|
currentLanguageTag: "th"
|
||||||
|
},
|
||||||
|
realm: { rememberMe: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithoutPasswordReset: Story = {
|
||||||
|
globals: {
|
||||||
|
kcContext: {
|
||||||
|
locale: {
|
||||||
|
currentLanguageTag: "th"
|
||||||
|
},
|
||||||
|
realm: { resetPasswordAllowed: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithEmailAsUsername: Story = {
|
||||||
|
globals: {
|
||||||
|
kcContext: {
|
||||||
|
locale: {
|
||||||
|
currentLanguageTag: "th"
|
||||||
|
},
|
||||||
|
realm: { loginWithEmailAllowed: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithPresetUsername: Story = {
|
||||||
|
globals: {
|
||||||
|
kcContext: {
|
||||||
|
locale: {
|
||||||
|
currentLanguageTag: "th"
|
||||||
|
},
|
||||||
|
login: { username: 'max.mustermann@mail.com' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithImmutablePresetUsername: Story = {
|
||||||
|
globals: {
|
||||||
|
kcContext: {
|
||||||
|
locale: {
|
||||||
|
currentLanguageTag: "th"
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
attemptedUsername: 'max.mustermann@mail.com',
|
||||||
|
showUsername: true,
|
||||||
|
},
|
||||||
|
usernameHidden: true,
|
||||||
|
message: {
|
||||||
|
type: 'info',
|
||||||
|
summary: 'Please re-authenticate to continue',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithSocialProviders: Story = {
|
||||||
|
globals: {
|
||||||
|
kcContext: {
|
||||||
|
locale: {
|
||||||
|
currentLanguageTag: "th"
|
||||||
|
},
|
||||||
|
social: {
|
||||||
|
displayInfo: true,
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
loginUrl: 'google',
|
||||||
|
alias: 'google',
|
||||||
|
providerId: 'google',
|
||||||
|
displayName: 'Google',
|
||||||
|
iconClasses: 'fa fa-google',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loginUrl: 'microsoft',
|
||||||
|
alias: 'microsoft',
|
||||||
|
providerId: 'microsoft',
|
||||||
|
displayName: 'Microsoft',
|
||||||
|
iconClasses: 'fa fa-windows',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loginUrl: 'facebook',
|
||||||
|
alias: 'facebook',
|
||||||
|
providerId: 'facebook',
|
||||||
|
displayName: 'Facebook',
|
||||||
|
iconClasses: 'fa fa-facebook',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loginUrl: 'github',
|
||||||
|
alias: 'github',
|
||||||
|
providerId: 'github',
|
||||||
|
displayName: 'Github',
|
||||||
|
iconClasses: 'fa fa-github',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithoutPasswordField: Story = {
|
||||||
|
globals: {
|
||||||
|
kcContext: {
|
||||||
|
locale: {
|
||||||
|
currentLanguageTag: "th"
|
||||||
|
},
|
||||||
|
realm: { password: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithErrorMessage: Story = {
|
||||||
|
globals: {
|
||||||
|
kcContext: {
|
||||||
|
locale: {
|
||||||
|
currentLanguageTag: "th"
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
summary:
|
||||||
|
'The time allotted for the connection has elapsed.<br/>The login process will restart from the beginning.',
|
||||||
|
type: 'error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithOneSocialProvider: Story = {
|
||||||
|
globals: {
|
||||||
|
kcContext: {
|
||||||
|
locale: {
|
||||||
|
currentLanguageTag: "th"
|
||||||
|
},
|
||||||
|
social: {
|
||||||
|
displayInfo: true,
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
loginUrl: 'google',
|
||||||
|
alias: 'google',
|
||||||
|
providerId: 'google',
|
||||||
|
displayName: 'Google',
|
||||||
|
iconClasses: 'fa fa-google',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithTwoSocialProviders: Story = {
|
||||||
|
globals: {
|
||||||
|
kcContext: {
|
||||||
|
locale: {
|
||||||
|
currentLanguageTag: "th"
|
||||||
|
},
|
||||||
|
social: {
|
||||||
|
displayInfo: true,
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
loginUrl: 'google',
|
||||||
|
alias: 'google',
|
||||||
|
providerId: 'google',
|
||||||
|
displayName: 'Google',
|
||||||
|
iconClasses: 'fa fa-google',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loginUrl: 'microsoft',
|
||||||
|
alias: 'microsoft',
|
||||||
|
providerId: 'microsoft',
|
||||||
|
displayName: 'Microsoft',
|
||||||
|
iconClasses: 'fa fa-windows',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithMoreThanTwoSocialProviders: Story = {
|
||||||
|
globals: {
|
||||||
|
kcContext: {
|
||||||
|
locale: {
|
||||||
|
currentLanguageTag: "th"
|
||||||
|
},
|
||||||
|
social: {
|
||||||
|
displayInfo: true,
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
loginUrl: 'google',
|
||||||
|
alias: 'google',
|
||||||
|
providerId: 'google',
|
||||||
|
displayName: 'Google',
|
||||||
|
iconClasses: 'fa fa-google',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loginUrl: 'microsoft',
|
||||||
|
alias: 'microsoft',
|
||||||
|
providerId: 'microsoft',
|
||||||
|
displayName: 'Microsoft',
|
||||||
|
iconClasses: 'fa fa-windows',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loginUrl: 'facebook',
|
||||||
|
alias: 'facebook',
|
||||||
|
providerId: 'facebook',
|
||||||
|
displayName: 'Facebook',
|
||||||
|
iconClasses: 'fa fa-facebook',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loginUrl: 'twitter',
|
||||||
|
alias: 'twitter',
|
||||||
|
providerId: 'twitter',
|
||||||
|
displayName: 'Twitter',
|
||||||
|
iconClasses: 'fa fa-twitter',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithSocialProvidersAndWithoutRememberMe: Story = {
|
||||||
|
globals: {
|
||||||
|
kcContext: {
|
||||||
|
locale: {
|
||||||
|
currentLanguageTag: "th"
|
||||||
|
},
|
||||||
|
social: {
|
||||||
|
displayInfo: true,
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
loginUrl: 'google',
|
||||||
|
alias: 'google',
|
||||||
|
providerId: 'google',
|
||||||
|
displayName: 'Google',
|
||||||
|
iconClasses: 'fa fa-google',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
realm: { rememberMe: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
34
src/main.ts
Normal file
34
src/main.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { bootstrapApplication } from '@angular/platform-browser';
|
||||||
|
import { bootstrapKcApplication } from './kc.gen';
|
||||||
|
import { appConfig } from './app.config';
|
||||||
|
|
||||||
|
// The following block can be uncommented to test a specific page with `yarn dev`
|
||||||
|
// Don't forget to comment back or your bundle size will increase
|
||||||
|
/*
|
||||||
|
import { getKcContextMock } from './login/KcPageStory';
|
||||||
|
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
window.kcContext = getKcContextMock({
|
||||||
|
pageId: 'login.ftl',
|
||||||
|
overrides: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
(async () => {
|
||||||
|
if (window.kcContext === undefined) {
|
||||||
|
const { NoContextComponent } = await import('./no-context.component');
|
||||||
|
|
||||||
|
bootstrapApplication(NoContextComponent, appConfig);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrapKcApplication({
|
||||||
|
kcContext: window.kcContext,
|
||||||
|
bootstrapApplication: ({ KcRootComponent, kcProvider }) =>
|
||||||
|
bootstrapApplication(KcRootComponent, {
|
||||||
|
...appConfig,
|
||||||
|
providers: [...appConfig.providers, kcProvider],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
})();
|
8
src/no-context.component.ts
Normal file
8
src/no-context.component.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'kc-root',
|
||||||
|
standalone: true,
|
||||||
|
template: `<h1>No Keycloak Context</h1>`,
|
||||||
|
})
|
||||||
|
export class NoContextComponent {}
|
6
src/test-setup.ts
Normal file
6
src/test-setup.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import '@analogjs/vitest-angular/setup-snapshots';
|
||||||
|
|
||||||
|
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
|
||||||
|
import { getTestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
1
src/vite-env.d.ts
vendored
Normal file
1
src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
11
tsconfig.app.json
Normal file
11
tsconfig.app.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compileOnSave": false,
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./out-tsc/app",
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"files": [],
|
||||||
|
"include": ["src/**/*.ts"]
|
||||||
|
}
|
32
tsconfig.json
Normal file
32
tsconfig.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"compileOnSave": false,
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "./",
|
||||||
|
"outDir": "./dist/out-tsc",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"declaration": false,
|
||||||
|
"downlevelIteration": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"isolatedModules": true,
|
||||||
|
"importHelpers": true,
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ES2022",
|
||||||
|
"lib": ["ES2022", "dom"],
|
||||||
|
"useDefineForClassFields": false,
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"enableI18nLegacyMessageIdFormat": false,
|
||||||
|
"strictInjectionParameters": true,
|
||||||
|
"strictInputAccessModifiers": true,
|
||||||
|
"strictTemplates": true
|
||||||
|
}
|
||||||
|
}
|
10
tsconfig.spec.json
Normal file
10
tsconfig.spec.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./out-tsc/spec",
|
||||||
|
"target": "es2016",
|
||||||
|
"types": ["vitest/globals", "node"]
|
||||||
|
},
|
||||||
|
"files": ["src/test-setup.ts"],
|
||||||
|
"include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
|
||||||
|
}
|
31
vite.config.ts
Normal file
31
vite.config.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/// <reference types="vitest" />
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import angular from '@analogjs/vite-plugin-angular';
|
||||||
|
import { keycloakify } from 'keycloakify/vite-plugin';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig(({ mode }) => ({
|
||||||
|
build: {
|
||||||
|
target: ['es2022'],
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
mainFields: ['module'],
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
angular(),
|
||||||
|
keycloakify({
|
||||||
|
accountThemeImplementation: 'none',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
environment: 'jsdom',
|
||||||
|
setupFiles: ['src/test-setup.ts'],
|
||||||
|
include: ['**/*.spec.ts'],
|
||||||
|
reporters: ['default'],
|
||||||
|
},
|
||||||
|
define: {
|
||||||
|
'import.meta.vitest': mode !== 'production',
|
||||||
|
},
|
||||||
|
}));
|
Loading…
x
Reference in New Issue
Block a user