Compare commits

..

37 Commits

Author SHA1 Message Date
f0cdb0b80b Fix build 2024-06-06 09:31:27 +02:00
2af953927e Release candidate 2024-06-06 09:14:27 +02:00
dcb9fbd0f7 fixes for the add-story command 2024-06-06 09:13:58 +02:00
5bc1f6479d fmt 2024-06-06 09:13:13 +02:00
f3e4bca468 Add script to copy over the stories 2024-06-06 07:41:01 +02:00
54645f5cff Update storybook setup for portability 2024-06-06 07:28:34 +02:00
a7f3e00821 Remove createKcContextMock from the index 2024-06-06 06:27:28 +02:00
108c281b0c Enable to eject Template.tsx and UserProfileFormFields.tsx 2024-06-06 06:12:05 +02:00
58892cbb56 Change first level build target of bin 2024-06-06 06:11:34 +02:00
dae1053ca8 Consistency with the starter 2024-06-06 06:10:41 +02:00
83a9778c30 Release candidate 2024-06-06 04:36:16 +02:00
c52157bfb9 Remove dependency to powerhooks 2024-06-06 04:35:57 +02:00
62bf846d5f Release candidate 2024-06-06 02:29:25 +02:00
148f7fa316 Rollback unarrowing of the getKcContextMock return type 2024-06-06 02:29:09 +02:00
f488327885 Release candidate 2024-06-06 01:31:17 +02:00
593b929254 #525 2024-06-06 01:31:00 +02:00
9218e97315 Relase candidate 2024-06-06 00:33:51 +02:00
beb0e8bd77 Add missing translations 2024-06-06 00:33:34 +02:00
cace66e9f8 No longer need for a eslint rule ignore for with default vite starter 2024-06-05 23:30:28 +02:00
ef850c71fd Release candidate 2024-06-05 23:24:44 +02:00
aa8dc1919f Make getKcContext mock return type less narrow 2024-06-05 23:24:21 +02:00
c7c9b19853 Fix case error 2024-06-05 23:08:40 +02:00
68c26e0f5b Fix case issue 2024-06-05 22:55:09 +02:00
6bcdf286ef Add missing export 2024-06-05 22:53:14 +02:00
d9345396e8 Fix build 2024-06-05 22:48:13 +02:00
4c423900d4 Release candidate 2024-06-05 21:44:54 +02:00
504419b26d Fix storybook 2024-06-05 21:44:26 +02:00
6e058eafed It's less confusing to use "#" as urls in mock kcContext 2024-06-05 21:44:14 +02:00
08fc9d8631 Fix tests by updating vitest 2024-06-05 21:23:34 +02:00
e8a11991a0 Rename kcContext -> KcContext and improve consistency 2024-06-05 21:13:58 +02:00
3e6d679838 Release candidate 2024-06-05 18:50:26 +02:00
4dad859c4d Fix storybook 2024-06-05 18:50:00 +02:00
ef9c933ca8 Relase candidate 2024-06-05 18:42:50 +02:00
0461190a67 Do not export default the Fallback component 2024-06-05 18:42:32 +02:00
06b3211b08 Ease up the instentiation of i18n 2024-06-05 18:41:53 +02:00
2033a9ce0c Release candidate 2024-06-05 18:15:25 +02:00
fca18d9209 Add missing file to the NPM bundle 2024-06-05 18:15:13 +02:00
141 changed files with 1794 additions and 1328 deletions

View File

@ -7,13 +7,11 @@
background-color: #393939;
}
body.sb-show-preparing-docs > .sb-wrapper {
visibility: hidden;
}
body .sb-preparing-story {
visibility: hidden;
}
</style>

View File

@ -1,6 +1,6 @@
{
"name": "keycloakify",
"version": "10.0.0-rc.24",
"version": "10.0.0-rc.34",
"description": "Create Keycloak themes using React",
"repository": {
"type": "git",
@ -46,6 +46,7 @@
"dist/bin/shared/constants.js.map",
"!dist/vite-plugin/",
"dist/vite-plugin/index.d.ts",
"dist/vite-plugin/vite-plugin.d.ts",
"dist/vite-plugin/index.js"
],
"keywords": [
@ -64,7 +65,6 @@
"react": "*"
},
"dependencies": {
"minimal-polyfills": "^2.2.3",
"react-markdown": "^5.0.3",
"tsafe": "^1.6.6"
},
@ -117,7 +117,7 @@
"tss-react": "^4.9.10",
"typescript": "^4.9.1-beta",
"vite": "^5.2.11",
"vitest": "^0.29.8",
"vitest": "^1.6.0",
"yauzl": "^2.10.0",
"zod": "^3.17.10",
"evt": "^2.5.7"

View File

@ -52,7 +52,25 @@ transformCodebase({
fs.rmSync(join("dist", "ncc_out"), { recursive: true });
patchDeprecatedBufferApiUsage(join("dist", "bin", "main.js"));
{
let hasBeenPatched = false;
fs.readdirSync(join("dist", "bin")).forEach(fileBasename => {
if (fileBasename !== "main.js" && !fileBasename.endsWith(".index.js")) {
return;
}
const { hasBeenPatched: hasBeenPatched_i } = patchDeprecatedBufferApiUsage(
join("dist", "bin", fileBasename)
);
if (hasBeenPatched_i) {
hasBeenPatched = true;
}
});
assert(hasBeenPatched);
}
fs.chmodSync(
join("dist", "bin", "main.js"),
@ -93,6 +111,10 @@ run(
)}`
);
fs.readdirSync(join("dist", "ncc_out")).forEach(fileBasename =>
assert(!fileBasename.endsWith(".index.js"))
);
transformCodebase({
srcDirPath: join("dist", "ncc_out"),
destDirPath: join("dist", "vite-plugin"),
@ -105,12 +127,30 @@ transformCodebase({
fs.rmSync(join("dist", "ncc_out"), { recursive: true });
patchDeprecatedBufferApiUsage(join("dist", "vite-plugin", "index.js"));
{
const { hasBeenPatched } = patchDeprecatedBufferApiUsage(
join("dist", "vite-plugin", "index.js")
);
assert(hasBeenPatched);
}
fs.rmSync(join("dist", "src"), { recursive: true, force: true });
fs.cpSync("src", join("dist", "src"), { recursive: true });
transformCodebase({
srcDirPath: join("stories"),
destDirPath: join("dist", "stories"),
transformSourceCode: ({ fileRelativePath, sourceCode }) => {
if (!fileRelativePath.endsWith(".stories.tsx")) {
return undefined;
}
return { modifiedSourceCode: sourceCode };
}
});
console.log(chalk.green(`✓ built in ${((Date.now() - startTime) / 1000).toFixed(2)}s`));
function run(command: string) {
@ -127,7 +167,9 @@ function patchDeprecatedBufferApiUsage(filePath: string) {
`var buffer = Buffer.allocUnsafe ? Buffer.allocUnsafe(toRead) : new Buffer(toRead);`
);
assert(after !== before, `Patch failed for ${relative(process.cwd(), filePath)}`);
fs.writeFileSync(filePath, Buffer.from(after, "utf8"));
const hasBeenPatched = after !== before;
return { hasBeenPatched };
}

View File

@ -6,10 +6,12 @@ import {
dirname as pathDirname,
sep as pathSep
} from "path";
import { assert } from "tsafe/assert";
import { same } from "evt/tools/inDepth";
import { crawl } from "../src/bin/tools/crawl";
import { downloadKeycloakDefaultTheme } from "../src/bin/shared/downloadKeycloakDefaultTheme";
import { getThisCodebaseRootDirPath } from "../src/bin/tools/getThisCodebaseRootDirPath";
import { rmSync } from "../src/bin/tools/fs.rmSync";
import { deepAssign } from "../src/tools/deepAssign";
// NOTE: To run without argument when we want to generate src/i18n/generated_kcMessages files,
// update the version array for generating for newer version.
@ -73,12 +75,35 @@ async function main() {
}
Object.keys(record).forEach(themeType => {
const recordForPageType = record[themeType];
if (themeType !== "login" && themeType !== "account") {
return;
}
const recordForThemeType = record[themeType];
const languages = Object.keys(recordForThemeType);
const keycloakifyExtraMessages = (() => {
switch (themeType) {
case "login":
return keycloakifyExtraMessages_login;
case "account":
return keycloakifyExtraMessages_account;
}
assert(false);
})();
assert(
same(languages, Object.keys(keycloakifyExtraMessages), {
takeIntoAccountArraysOrdering: false
})
);
deepAssign({
target: recordForThemeType,
source: keycloakifyExtraMessages
});
const baseMessagesDirPath = pathJoin(
thisCodebaseRootDirPath,
"src",
@ -87,8 +112,6 @@ async function main() {
"baseMessages"
);
const languages = Object.keys(recordForPageType);
const generatedFileHeader = [
`//This code was automatically generated by running ${pathRelative(
thisCodebaseRootDirPath,
@ -110,7 +133,7 @@ async function main() {
"",
"/* spell-checker: disable */",
`const messages= ${JSON.stringify(
recordForPageType[language],
recordForThemeType[language],
null,
2
)};`,
@ -154,6 +177,491 @@ async function main() {
});
}
const keycloakifyExtraMessages_login: Record<
| "en"
| "ar"
| "ca"
| "cs"
| "da"
| "de"
| "el"
| "es"
| "fa"
| "fi"
| "fr"
| "hu"
| "it"
| "ja"
| "lt"
| "lv"
| "nl"
| "no"
| "pl"
| "pt-BR"
| "ru"
| "sk"
| "sv"
| "th"
| "tr"
| "uk"
| "zh-CN",
Record<
| "shouldBeEqual"
| "shouldBeDifferent"
| "shouldMatchPattern"
| "mustBeAnInteger"
| "notAValidOption"
| "selectAnOption"
| "remove"
| "addValue"
| "languages",
string
>
> = {
en: {
shouldBeEqual: "{0} should be equal to {1}",
shouldBeDifferent: "{0} should be different to {1}",
shouldMatchPattern: "Pattern should match: `/{0}/`",
mustBeAnInteger: "Must be an integer",
notAValidOption: "Not a valid option",
selectAnOption: "Select an option",
remove: "Remove",
addValue: "Add value",
languages: "Languages"
},
/* spell-checker: disable */
ar: {
shouldBeEqual: "{0} يجب أن يكون مساويًا لـ {1}",
shouldBeDifferent: "{0} يجب أن يكون مختلفًا عن {1}",
shouldMatchPattern: "`/يجب أن يطابق النمط: `/{0}/",
mustBeAnInteger: "يجب أن يكون عددًا صحيحًا",
notAValidOption: "ليس خيارًا صالحًا",
selectAnOption: "اختر خيارًا",
remove: "إزالة",
addValue: "أضف قيمة",
languages: "اللغات"
},
ca: {
shouldBeEqual: "{0} hauria de ser igual a {1}",
shouldBeDifferent: "{0} hauria de ser diferent de {1}",
shouldMatchPattern: "El patró hauria de coincidir: `/{0}/`",
mustBeAnInteger: "Ha de ser un enter",
notAValidOption: "No és una opció vàlida",
selectAnOption: "Selecciona una opció",
remove: "Elimina",
addValue: "Afegeix valor",
languages: "Idiomes"
},
cs: {
shouldBeEqual: "{0} by měl být roven {1}",
shouldBeDifferent: "{0} by měl být odlišný od {1}",
shouldMatchPattern: "Vzor by měl odpovídat: `/{0}/`",
mustBeAnInteger: "Musí být celé číslo",
notAValidOption: "Není platná možnost",
selectAnOption: "Vyberte možnost",
remove: "Odstranit",
addValue: "Přidat hodnotu",
languages: "Jazyky"
},
da: {
shouldBeEqual: "{0} bør være lig med {1}",
shouldBeDifferent: "{0} bør være forskellig fra {1}",
shouldMatchPattern: "Mønsteret bør matche: `/{0}/`",
mustBeAnInteger: "Skal være et heltal",
notAValidOption: "Ikke en gyldig mulighed",
selectAnOption: "Vælg en mulighed",
remove: "Fjern",
addValue: "Tilføj værdi",
languages: "Sprog"
},
de: {
shouldBeEqual: "{0} sollte gleich {1} sein",
shouldBeDifferent: "{0} sollte sich von {1} unterscheiden",
shouldMatchPattern: "Muster sollte übereinstimmen: `/{0}/`",
mustBeAnInteger: "Muss eine ganze Zahl sein",
notAValidOption: "Keine gültige Option",
selectAnOption: "Wählen Sie eine Option",
remove: "Entfernen",
addValue: "Wert hinzufügen",
languages: "Sprachen"
},
el: {
shouldBeEqual: "Το {0} πρέπει να είναι ίσο με {1}",
shouldBeDifferent: "Το {0} πρέπει να διαφέρει από το {1}",
shouldMatchPattern: "Το πρότυπο πρέπει να ταιριάζει: `/{0}/`",
mustBeAnInteger: "Πρέπει να είναι ακέραιος",
notAValidOption: "Δεν είναι μια έγκυρη επιλογή",
selectAnOption: "Επιλέξτε μια επιλογή",
remove: "Αφαίρεση",
addValue: "Προσθήκη τιμής",
languages: "Γλώσσες"
},
es: {
shouldBeEqual: "{0} debería ser igual a {1}",
shouldBeDifferent: "{0} debería ser diferente a {1}",
shouldMatchPattern: "El patrón debería coincidir: `/{0}/`",
mustBeAnInteger: "Debe ser un número entero",
notAValidOption: "No es una opción válida",
selectAnOption: "Selecciona una opción",
remove: "Eliminar",
addValue: "Añadir valor",
languages: "Idiomas"
},
fa: {
shouldBeEqual: "{0} باید برابر باشد با {1}",
shouldBeDifferent: "{0} باید متفاوت باشد از {1}",
shouldMatchPattern: "الگو باید مطابقت داشته باشد: `/{0}/`",
mustBeAnInteger: "باید یک عدد صحیح باشد",
notAValidOption: "یک گزینه معتبر نیست",
selectAnOption: "یک گزینه انتخاب کنید",
remove: "حذف",
addValue: "افزودن مقدار",
languages: "زبان‌ها"
},
fi: {
shouldBeEqual: "{0} pitäisi olla yhtä suuri kuin {1}",
shouldBeDifferent: "{0} pitäisi olla erilainen kuin {1}",
shouldMatchPattern: "Mallin tulisi vastata: `/{0}/`",
mustBeAnInteger: "On oltava kokonaisluku",
notAValidOption: "Ei ole kelvollinen vaihtoehto",
selectAnOption: "Valitse vaihtoehto",
remove: "Poista",
addValue: "Lisää arvo",
languages: "Kielet"
},
fr: {
shouldBeEqual: "{0} devrait être égal à {1}",
shouldBeDifferent: "{0} devrait être différent de {1}",
shouldMatchPattern: "Le motif devrait correspondre: `/{0}/`",
mustBeAnInteger: "Doit être un entier",
notAValidOption: "Pas une option valide",
selectAnOption: "Sélectionnez une option",
remove: "Supprimer",
addValue: "Ajouter une valeur",
languages: "Langues"
},
hu: {
shouldBeEqual: "{0} egyenlő kell legyen {1}-vel",
shouldBeDifferent: "{0} különbözőnek kell lennie, mint {1}",
shouldMatchPattern: "A mintának egyeznie kell: `/{0}/`",
mustBeAnInteger: "Egész számnak kell lennie",
notAValidOption: "Nem érvényes opció",
selectAnOption: "Válasszon egy lehetőséget",
remove: "Eltávolítás",
addValue: "Érték hozzáadása",
languages: "Nyelvek"
},
it: {
shouldBeEqual: "{0} dovrebbe essere uguale a {1}",
shouldBeDifferent: "{0} dovrebbe essere diverso da {1}",
shouldMatchPattern: "Il modello dovrebbe corrispondere: `/{0}/`",
mustBeAnInteger: "Deve essere un numero intero",
notAValidOption: "Non è un'opzione valida",
selectAnOption: "Seleziona un'opzione",
remove: "Rimuovi",
addValue: "Aggiungi valore",
languages: "Lingue"
},
ja: {
shouldBeEqual: "{0} は {1} と等しい必要があります",
shouldBeDifferent: "{0} は {1} と異なる必要があります",
shouldMatchPattern: "パターンは一致する必要があります: `/{0}/`",
mustBeAnInteger: "整数である必要があります",
notAValidOption: "有効なオプションではありません",
selectAnOption: "オプションを選択",
remove: "削除",
addValue: "値を追加",
languages: "言語"
},
lt: {
shouldBeEqual: "{0} turėtų būti lygus {1}",
shouldBeDifferent: "{0} turėtų skirtis nuo {1}",
shouldMatchPattern: "Šablonas turėtų atitikti: `/{0}/`",
mustBeAnInteger: "Turi būti sveikasis skaičius",
notAValidOption: "Netinkama parinktis",
selectAnOption: "Pasirinkite parinktį",
remove: "Pašalinti",
addValue: "Pridėti reikšmę",
languages: "Kalbos"
},
lv: {
shouldBeEqual: "{0} jābūt vienādam ar {1}",
shouldBeDifferent: "{0} jābūt atšķirīgam no {1}",
shouldMatchPattern: "Mustrim jāsakrīt: `/{0}/`",
mustBeAnInteger: "Jābūt veselam skaitlim",
notAValidOption: "Nav derīga opcija",
selectAnOption: "Izvēlieties opciju",
remove: "Noņemt",
addValue: "Pievienot vērtību",
languages: "Valodas"
},
nl: {
shouldBeEqual: "{0} moet gelijk zijn aan {1}",
shouldBeDifferent: "{0} moet verschillen van {1}",
shouldMatchPattern: "Patroon moet overeenkomen: `/{0}/`",
mustBeAnInteger: "Moet een geheel getal zijn",
notAValidOption: "Geen geldige optie",
selectAnOption: "Selecteer een optie",
remove: "Verwijderen",
addValue: "Waarde toevoegen",
languages: "Talen"
},
no: {
shouldBeEqual: "{0} skal være lik {1}",
shouldBeDifferent: "{0} skal være forskjellig fra {1}",
shouldMatchPattern: "Mønsteret skal matche: `/{0}/`",
mustBeAnInteger: "Må være et heltall",
notAValidOption: "Ikke et gyldig alternativ",
selectAnOption: "Velg et alternativ",
remove: "Fjern",
addValue: "Legg til verdi",
languages: "Språk"
},
pl: {
shouldBeEqual: "{0} powinno być równe {1}",
shouldBeDifferent: "{0} powinno być różne od {1}",
shouldMatchPattern: "Wzór pow inien pasować: `/{0}/`",
mustBeAnInteger: "Musi być liczbą całkowitą",
notAValidOption: "Nieprawidłowa opcja",
selectAnOption: "Wybierz opcję",
remove: "Usuń",
addValue: "Dodaj wartość",
languages: "Języki"
},
"pt-BR": {
shouldBeEqual: "{0} deve ser igual a {1}",
shouldBeDifferent: "{0} deve ser diferente de {1}",
shouldMatchPattern: "O padrão deve corresponder: `/{0}/`",
mustBeAnInteger: "Deve ser um número inteiro",
notAValidOption: "Não é uma opção válida",
selectAnOption: "Selecione uma opção",
remove: "Remover",
addValue: "Adicionar valor",
languages: "Idiomas"
},
ru: {
shouldBeEqual: "{0} должно быть равно {1}",
shouldBeDifferent: "{0} должно отличаться от {1}",
shouldMatchPattern: "Шаблон должен соответствовать: `/{0}/`",
mustBeAnInteger: "Должно быть целым числом",
notAValidOption: "Недопустимый вариант",
selectAnOption: "Выберите вариант",
remove: "Удалить",
addValue: "Добавить значение",
languages: "Языки"
},
sk: {
shouldBeEqual: "{0} by mal byť rovnaký ako {1}",
shouldBeDifferent: "{0} by mal byť odlišný od {1}",
shouldMatchPattern: "Vzor by mal zodpovedať: `/{0}/`",
mustBeAnInteger: "Musí byť celé číslo",
notAValidOption: "Nie je platná možnosť",
selectAnOption: "Vyberte možnosť",
remove: "Odstrániť",
addValue: "Pridať hodnotu",
languages: "Jazyky"
},
sv: {
shouldBeEqual: "{0} bör vara lika med {1}",
shouldBeDifferent: "{0} bör vara annorlunda än {1}",
shouldMatchPattern: "Mönstret bör matcha: `/{0}/`",
mustBeAnInteger: "Måste vara ett heltal",
notAValidOption: "Inte ett giltigt alternativ",
selectAnOption: "Välj ett alternativ",
remove: "Ta bort",
addValue: "Lägg till värde",
languages: "Språk"
},
th: {
shouldBeEqual: "{0} ควรเท่ากับ {1}",
shouldBeDifferent: "{0} ควรแตกต่างจาก {1}",
shouldMatchPattern: "รูปแบบควรตรงกับ: `/{0}/`",
mustBeAnInteger: "ต้องเป็นจำนวนเต็ม",
notAValidOption: "ไม่ใช่ตัวเลือกที่ถูกต้อง",
selectAnOption: "เลือกตัวเลือก",
remove: "ลบ",
addValue: "เพิ่มค่า",
languages: "ภาษา"
},
tr: {
shouldBeEqual: "{0} {1} eşit olmalıdır",
shouldBeDifferent: "{0} {1} farklı olmalıdır",
shouldMatchPattern: "Desen eşleşmelidir: `/{0}/`",
mustBeAnInteger: "Tam sayı olmalıdır",
notAValidOption: "Geçerli bir seçenek değil",
selectAnOption: "Bir seçenek seçin",
remove: "Kaldır",
addValue: "Değer ekle",
languages: "Diller"
},
uk: {
shouldBeEqual: "{0} повинно бути рівним {1}",
shouldBeDifferent: "{0} повинно відрізнятися від {1}",
shouldMatchPattern: "Шаблон повинен відповідати: `/{0}/`",
mustBeAnInteger: "Повинно бути цілим числом",
notAValidOption: "Не є дійсною опцією",
selectAnOption: "Виберіть опцію",
remove: "Видалити",
addValue: "Додати значення",
languages: "Мови"
},
"zh-CN": {
shouldBeEqual: "{0} 应该等于 {1}",
shouldBeDifferent: "{0} 应该不同于 {1}",
shouldMatchPattern: "模式应匹配: `/{0}/`",
mustBeAnInteger: "必须是整数",
notAValidOption: "不是有效选项",
selectAnOption: "选择一个选项",
remove: "移除",
addValue: "添加值",
languages: "语言"
}
/* spell-checker: enable */
};
const keycloakifyExtraMessages_account: Record<
| "en"
| "ar"
| "ca"
| "cs"
| "da"
| "de"
| "el"
| "es"
| "fa"
| "fi"
| "fr"
| "hu"
| "it"
| "ja"
| "lt"
| "lv"
| "nl"
| "no"
| "pl"
| "pt-BR"
| "ru"
| "sk"
| "sv"
| "th"
| "tr"
| "uk"
| "zh-CN",
Record<"newPasswordSameAsOld" | "passwordConfirmNotMatch", string>
> = {
en: {
newPasswordSameAsOld: "New password must be different from the old one",
passwordConfirmNotMatch: "Password confirmation does not match"
},
/* spell-checker: disable */
ar: {
newPasswordSameAsOld: "يجب أن تكون كلمة المرور الجديدة مختلفة عن القديمة",
passwordConfirmNotMatch: "تأكيد كلمة المرور لا يتطابق"
},
ca: {
newPasswordSameAsOld: "La nova contrasenya ha de ser diferent de l'anterior",
passwordConfirmNotMatch: "La confirmació de la contrasenya no coincideix"
},
cs: {
newPasswordSameAsOld: "Nové heslo musí být odlišné od starého",
passwordConfirmNotMatch: "Potvrzení hesla se neshoduje"
},
da: {
newPasswordSameAsOld: "Det nye kodeord skal være forskelligt fra det gamle",
passwordConfirmNotMatch: "Adgangskodebekræftelse matcher ikke"
},
de: {
newPasswordSameAsOld: "Das neue Passwort muss sich vom alten unterscheiden",
passwordConfirmNotMatch: "Passwortbestätigung stimmt nicht überein"
},
el: {
newPasswordSameAsOld: "Ο νέος κωδικός πρόσβασης πρέπει να διαφέρει από τον παλιό",
passwordConfirmNotMatch: "Η επιβεβαίωση του κωδικού πρόσβασης δεν ταιριάζει"
},
es: {
newPasswordSameAsOld: "La nueva contraseña debe ser diferente de la anterior",
passwordConfirmNotMatch: "La confirmación de la contraseña no coincide"
},
fa: {
newPasswordSameAsOld: "رمز عبور جدید باید با رمز عبور قبلی متفاوت باشد",
passwordConfirmNotMatch: "تأیید رمز عبور مطابقت ندارد"
},
fi: {
newPasswordSameAsOld: "Uusi salasana on oltava erilainen kuin vanha",
passwordConfirmNotMatch: "Salasanan vahvistus ei täsmää"
},
fr: {
newPasswordSameAsOld: "Le nouveau mot de passe doit être différent de l'ancien",
passwordConfirmNotMatch: "La confirmation du mot de passe ne correspond pas"
},
hu: {
newPasswordSameAsOld: "Az új jelszónak különböznie kell az előzőtől",
passwordConfirmNotMatch: "A jelszó megerősítése nem egyezik"
},
it: {
newPasswordSameAsOld:
"La nuova password deve essere diversa da quella precedente",
passwordConfirmNotMatch: "La conferma della password non corrisponde"
},
ja: {
newPasswordSameAsOld: "新しいパスワードは古いパスワードと異なる必要があります",
passwordConfirmNotMatch: "パスワード確認が一致しません"
},
lt: {
newPasswordSameAsOld: "Naujas slaptažodis turi skirtis nuo seno",
passwordConfirmNotMatch: "Slaptažodžio patvirtinimas neatitinka"
},
lv: {
newPasswordSameAsOld: "Jaunajam parolam jābūt atšķirīgam no vecā",
passwordConfirmNotMatch: "Paroles apstiprināšana neatbilst"
},
nl: {
newPasswordSameAsOld: "Het nieuwe wachtwoord moet verschillend zijn van het oude",
passwordConfirmNotMatch: "Wachtwoordbevestiging komt niet overeen"
},
no: {
newPasswordSameAsOld: "Det nye passordet må være forskjellig fra det gamle",
passwordConfirmNotMatch: "Passordbekreftelsen stemmer ikke"
},
pl: {
newPasswordSameAsOld: "Nowe hasło musi być inne niż stare",
passwordConfirmNotMatch: "Potwierdzenie hasła nie pasuje"
},
"pt-BR": {
newPasswordSameAsOld: "A nova senha deve ser diferente da antiga",
passwordConfirmNotMatch: "A confirmação da senha não corresponde"
},
ru: {
newPasswordSameAsOld: "Новый пароль должен отличаться от старого",
passwordConfirmNotMatch: "Подтверждение пароля не совпадает"
},
sk: {
newPasswordSameAsOld: "Nové heslo musí byť odlišné od starého",
passwordConfirmNotMatch: "Potvrdenie hesla sa nezhoduje"
},
sv: {
newPasswordSameAsOld: "Det nya lösenordet måste skilja sig från det gamla",
passwordConfirmNotMatch: "Lösenordsbekräftelsen matchar inte"
},
th: {
newPasswordSameAsOld: "รหัสผ่านใหม่ต้องต่างจากรหัสผ่านเดิม",
passwordConfirmNotMatch: "การยืนยันรหัสผ่านไม่ตรงกัน"
},
tr: {
newPasswordSameAsOld: "Yeni şifre eskisinden farklı olmalıdır",
passwordConfirmNotMatch: "Şifre doğrulama eşleşmiyor"
},
uk: {
newPasswordSameAsOld: "Новий пароль повинен відрізнятися від старого",
passwordConfirmNotMatch: "Підтвердження пароля не співпадає"
},
"zh-CN": {
newPasswordSameAsOld: "新密码必须与旧密码不同",
passwordConfirmNotMatch: "密码确认不匹配"
}
/* spell-checker: enable */
};
if (require.main === module) {
main();
}

View File

@ -28,7 +28,7 @@ export function startRebuildOnSrcChange() {
console.log(chalk.green("Watching for changes in src/"));
chokidar.watch("src", { ignoreInitial: true }).on("all", async () => {
chokidar.watch(["src", "stories"], { ignoreInitial: true }).on("all", async () => {
await waitForDebounce();
runYarnBuild();

View File

@ -1,9 +1,8 @@
import { lazy, Suspense } from "react";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { I18n } from "keycloakify/account/i18n";
import type { KcContext } from "./kcContext";
import { assert, type Equals } from "tsafe/assert";
import FederatedIdentity from "./pages/FederatedIdentity";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { KcContext } from "./KcContext";
import type { I18n } from "./i18n";
const Password = lazy(() => import("keycloakify/account/pages/Password"));
const Account = lazy(() => import("keycloakify/account/pages/Account"));
@ -11,6 +10,7 @@ const Sessions = lazy(() => import("keycloakify/account/pages/Sessions"));
const Totp = lazy(() => import("keycloakify/account/pages/Totp"));
const Applications = lazy(() => import("keycloakify/account/pages/Applications"));
const Log = lazy(() => import("keycloakify/account/pages/Log"));
const FederatedIdentity = lazy(() => import("keycloakify/account/pages/FederatedIdentity"));
export default function Fallback(props: PageProps<KcContext, I18n>) {
const { kcContext, ...rest } = props;

View File

@ -1,4 +1,4 @@
import "minimal-polyfills/Object.fromEntries";
import "keycloakify/tools/Object.fromEntries";
import { resources_common, keycloak_resources } from "keycloakify/bin/shared/constants";
import { id } from "tsafe/id";
import type { KcContext } from "./KcContext";

View File

@ -1,12 +1,12 @@
import { useEffect } from "react";
import { assert } from "keycloakify/tools/assert";
import { clsx } from "keycloakify/tools/clsx";
import { type TemplateProps } from "keycloakify/account/TemplateProps";
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
import { useInsertLinkTags } from "keycloakify/tools/useInsertLinkTags";
import { useSetClassName } from "keycloakify/tools/useSetClassName";
import type { KcContext } from "./kcContext";
import type { TemplateProps } from "keycloakify/account/TemplateProps";
import type { KcContext } from "./KcContext";
import type { I18n } from "./i18n";
import { assert } from "keycloakify/tools/assert";
export default function Template(props: TemplateProps<KcContext, I18n>) {
const { kcContext, i18n, doUseDefaultCss, active, classes, children } = props;
@ -73,7 +73,6 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
{realm.internationalizationEnabled && (assert(locale !== undefined), true) && locale.supported.length > 1 && (
<li>
<div className="kc-dropdown" id="kc-locale-dropdown">
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a href="#" id="kc-current-locale-link">
{labelBySupportedLanguageTag[currentLanguageTag]}
</a>

View File

@ -1,5 +1,5 @@
import type { ReactNode } from "react";
import type { KcContext } from "./kcContext";
import type { KcContext } from "./KcContext";
import type { I18n } from "./i18n";
export type TemplateProps<

View File

@ -1,9 +1,10 @@
import "minimal-polyfills/Object.fromEntries";
import "keycloakify/tools/Object.fromEntries";
import { useEffect, useState, useRef } from "react";
import fallbackMessages from "./baseMessages/en";
import { getMessages } from "./baseMessages";
import { assert } from "tsafe/assert";
import type { KcContext } from "../kcContext/KcContext";
import type { KcContext } from "../KcContext";
import { Reflect } from "tsafe/Reflect";
export const fallbackLanguageTag = "en";
@ -16,7 +17,7 @@ export type KcContextLike = {
assert<KcContext extends KcContextLike ? true : false>();
export type MessageKey = keyof typeof fallbackMessages | keyof (typeof keycloakifyExtraMessages)[typeof fallbackLanguageTag];
export type MessageKey = keyof typeof fallbackMessages;
export type GenericI18n<MessageKey extends string> = {
/**
@ -106,12 +107,10 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
...createI18nTranslationFunctions({
fallbackMessages: {
...fallbackMessages,
...(keycloakifyExtraMessages[fallbackLanguageTag] ?? {}),
...(extraMessages[fallbackLanguageTag] ?? {})
} as any,
messages: {
...(await getMessages(currentLanguageTag)),
...((keycloakifyExtraMessages as any)[currentLanguageTag] ?? {}),
...(extraMessages[currentLanguageTag] ?? {})
} as any
}),
@ -137,7 +136,10 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
return i18n ?? null;
}
return { useI18n };
return {
useI18n,
ofTypeI18n: Reflect<GenericI18n<MessageKey | ExtraMessageKey>>()
};
}
function createI18nTranslationFunctions<MessageKey extends string>(params: {
@ -240,30 +242,3 @@ function createI18nTranslationFunctions<MessageKey extends string>(params: {
}) as string
};
}
const keycloakifyExtraMessages = {
en: {
shouldBeEqual: "{0} should be equal to {1}",
shouldBeDifferent: "{0} should be different to {1}",
shouldMatchPattern: "Pattern should match: `/{0}/`",
mustBeAnInteger: "Must be an integer",
notAValidOption: "Not a valid option",
newPasswordSameAsOld: "New password must be different from the old one",
passwordConfirmNotMatch: "Password confirmation does not match"
},
fr: {
/* spell-checker: disable */
shouldBeEqual: "{0} doit être égal à {1}",
shouldBeDifferent: "{0} doit être différent de {1}",
shouldMatchPattern: "Dois respecter le schéma: `/{0}/`",
mustBeAnInteger: "Doit être un nombre entier",
notAValidOption: "N'est pas une option valide",
logoutConfirmTitle: "Déconnexion",
logoutConfirmHeader: "Êtes-vous sûr(e) de vouloir vous déconnecter ?",
doLogout: "Se déconnecter",
newPasswordSameAsOld: "Le nouveau mot de passe doit être différent de l'ancien",
passwordConfirmNotMatch: "La confirmation du mot de passe ne correspond pas"
/* spell-checker: enable */
}
};

View File

@ -1 +1,2 @@
export type { I18n } from "./i18n";
export { createUseI18n } from "./i18n";

View File

@ -1,10 +1,3 @@
import Fallback from "keycloakify/account/Fallback";
export default Fallback;
export type { AccountThemePageId as PageId } from "keycloakify/bin/shared/constants";
export { createUseI18n } from "keycloakify/account/i18n/i18n";
export type { ExtendKcContext } from "keycloakify/account/kcContext";
export { createGetKcContextMock } from "keycloakify/account/kcContext";
export type { ExtendKcContext } from "keycloakify/account/KcContext";
export type { PageProps } from "keycloakify/account/pages/PageProps";
export { createUseI18n } from "keycloakify/account/i18n";

View File

@ -1,7 +1,7 @@
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function Account(props: PageProps<Extract<KcContext, { pageId: "account.ftl" }>, I18n>) {

View File

@ -1,13 +1,9 @@
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
function isArrayWithEmptyObject(variable: any): boolean {
return Array.isArray(variable) && variable.length === 1 && typeof variable[0] === "object" && Object.keys(variable[0]).length === 0;
}
export default function Applications(props: PageProps<Extract<KcContext, { pageId: "applications.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, classes, Template } = props;
@ -136,3 +132,7 @@ export default function Applications(props: PageProps<Extract<KcContext, { pageI
</Template>
);
}
function isArrayWithEmptyObject(variable: any): boolean {
return Array.isArray(variable) && variable.length === 1 && typeof variable[0] === "object" && Object.keys(variable[0]).length === 0;
}

View File

@ -1,6 +1,6 @@
import { PageProps } from "keycloakify/account";
import { I18n } from "keycloakify/account/i18n";
import { KcContext } from "keycloakify/account/kcContext";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function FederatedIdentity(props: PageProps<Extract<KcContext, { pageId: "federatedIdentity.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, classes, Template } = props;

View File

@ -1,8 +1,8 @@
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { KcContext } from "../kcContext";
import type { I18n } from "../i18n";
import { Key } from "react";
import type { Key } from "react";
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { I18n } from "../i18n";
import type { KcContext } from "../KcContext";
export default function Log(props: PageProps<Extract<KcContext, { pageId: "log.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, classes, Template } = props;

View File

@ -1,11 +1,11 @@
import type { I18n } from "keycloakify/account/i18n";
import type { TemplateProps, ClassKey } from "keycloakify/account/TemplateProps";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import type { KcContext } from "keycloakify/account/kcContext";
import type { I18n } from "../i18n";
import type { KcContext } from "../KcContext";
export type PageProps<NarowedKcContext = KcContext, I18nExtended extends I18n = I18n> = {
export type PageProps<NarrowedKcContext = KcContext, I18nExtended extends I18n = I18n> = {
Template: LazyOrNot<(props: TemplateProps<any, any>) => JSX.Element | null>;
kcContext: NarowedKcContext;
kcContext: NarrowedKcContext;
i18n: I18nExtended;
doUseDefaultCss: boolean;
classes?: Partial<Record<ClassKey, string>>;

View File

@ -1,8 +1,8 @@
import { useState } from "react";
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function Password(props: PageProps<Extract<KcContext, { pageId: "password.ftl" }>, I18n>) {

View File

@ -1,7 +1,7 @@
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function Sessions(props: PageProps<Extract<KcContext, { pageId: "sessions.ftl" }>, I18n>) {

View File

@ -1,7 +1,7 @@
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { PageProps } from "keycloakify/account/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function Totp(props: PageProps<Extract<KcContext, { pageId: "totp.ftl" }>, I18n>) {

109
src/bin/add-story.ts Normal file
View File

@ -0,0 +1,109 @@
import { getThisCodebaseRootDirPath } from "./tools/getThisCodebaseRootDirPath";
import cliSelect from "cli-select";
import {
loginThemePageIds,
accountThemePageIds,
type LoginThemePageId,
type AccountThemePageId,
themeTypes,
type ThemeType
} from "./shared/constants";
import { capitalize } from "tsafe/capitalize";
import * as fs from "fs";
import { join as pathJoin, relative as pathRelative, dirname as pathDirname } from "path";
import { kebabCaseToCamelCase } from "./tools/kebabCaseToSnakeCase";
import { assert, Equals } from "tsafe/assert";
import { getThemeSrcDirPath } from "./shared/getThemeSrcDirPath";
import type { CliCommandOptions } from "./main";
import { readBuildOptions } from "./shared/buildOptions";
import chalk from "chalk";
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
const { cliCommandOptions } = params;
const buildOptions = readBuildOptions({
cliCommandOptions
});
console.log(chalk.cyan("Theme type:"));
const { value: themeType } = await cliSelect<ThemeType>({
values: [...themeTypes]
}).catch(() => {
process.exit(-1);
});
console.log(`${themeType}`);
console.log(chalk.cyan("Select the page you want to create a Storybook for:"));
const { value: pageId } = await cliSelect<LoginThemePageId | AccountThemePageId>({
values: (() => {
switch (themeType) {
case "login":
return [...loginThemePageIds];
case "account":
return [...accountThemePageIds];
}
assert<Equals<typeof themeType, never>>(false);
})()
}).catch(() => {
process.exit(-1);
});
console.log(`${pageId}`);
const { themeSrcDirPath } = getThemeSrcDirPath({
reactAppRootDirPath: buildOptions.reactAppRootDirPath
});
const componentBasename = capitalize(kebabCaseToCamelCase(pageId)).replace(
/ftl$/,
"stories.tsx"
);
const targetFilePath = pathJoin(
themeSrcDirPath,
themeType,
"pages",
componentBasename
);
if (fs.existsSync(targetFilePath)) {
console.log(`${pathRelative(process.cwd(), targetFilePath)} already exists`);
process.exit(-1);
}
const componentCode = fs
.readFileSync(
pathJoin(
getThisCodebaseRootDirPath(),
"stories",
themeType,
"pages",
componentBasename
)
)
.toString("utf8")
.replace('import React from "react";\n', "");
{
const targetDirPath = pathDirname(targetFilePath);
if (!fs.existsSync(targetDirPath)) {
fs.mkdirSync(targetDirPath, { recursive: true });
}
}
fs.writeFileSync(targetFilePath, Buffer.from(componentCode, "utf8"));
console.log(
[
`${chalk.green("✓")} ${chalk.bold(
pathJoin(".", pathRelative(process.cwd(), targetFilePath))
)} copy pasted from the Keycloakify source code into your project`,
`You can start storybook with ${chalk.bold("yarn storybook")}`
].join("\n")
);
}

View File

@ -39,13 +39,26 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
console.log(chalk.cyan("Select the page you want to customize:"));
const { value: pageId } = await cliSelect<LoginThemePageId | AccountThemePageId>({
const templateValue = "Template.tsx (Layout common to every page)";
const userProfileFormFieldsValue =
"UserProfileFormFields.tsx (Renders the form of the register.ftl, login-update-profile.ftl, update-email.ftl and idp-review-user-profile.ftl)";
const { value: pageIdOrComponent } = await cliSelect<
| LoginThemePageId
| AccountThemePageId
| typeof templateValue
| typeof userProfileFormFieldsValue
>({
values: (() => {
switch (themeType) {
case "login":
return [...loginThemePageIds];
return [
templateValue,
userProfileFormFieldsValue,
...loginThemePageIds
];
case "account":
return [...accountThemePageIds];
return [templateValue, ...accountThemePageIds];
}
assert<Equals<typeof themeType, never>>(false);
})()
@ -53,27 +66,45 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
process.exit(-1);
});
console.log(`${pageId}`);
const componentPageBasename = capitalize(kebabCaseToCamelCase(pageId)).replace(
/ftl$/,
"tsx"
);
console.log(`${pageIdOrComponent}`);
const { themeSrcDirPath } = getThemeSrcDirPath({
reactAppRootDirPath: buildOptions.reactAppRootDirPath
});
const componentBasename = (() => {
if (pageIdOrComponent === templateValue) {
return "Template.tsx";
}
if (pageIdOrComponent === userProfileFormFieldsValue) {
return "UserProfileFormFields.tsx";
}
return capitalize(kebabCaseToCamelCase(pageIdOrComponent)).replace(/ftl$/, "tsx");
})();
const pagesOrDot = (() => {
if (
pageIdOrComponent === templateValue ||
pageIdOrComponent === userProfileFormFieldsValue
) {
return ".";
}
return "pages";
})();
const targetFilePath = pathJoin(
themeSrcDirPath,
themeType,
"pages",
componentPageBasename
pagesOrDot,
componentBasename
);
if (fs.existsSync(targetFilePath)) {
console.log(
`${pageId} is already ejected, ${pathRelative(
`${pageIdOrComponent} is already ejected, ${pathRelative(
process.cwd(),
targetFilePath
)} already exists`
@ -82,6 +113,18 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
process.exit(-1);
}
const componentCode = fs
.readFileSync(
pathJoin(
getThisCodebaseRootDirPath(),
"src",
themeType,
pagesOrDot,
componentBasename
)
)
.toString("utf8");
{
const targetDirPath = pathDirname(targetFilePath);
@ -90,28 +133,66 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
}
}
const componentPageContent = fs
.readFileSync(
pathJoin(
getThisCodebaseRootDirPath(),
"src",
themeType,
"pages",
componentPageBasename
)
)
.toString("utf8");
fs.writeFileSync(targetFilePath, Buffer.from(componentCode, "utf8"));
fs.writeFileSync(targetFilePath, Buffer.from(componentPageContent, "utf8"));
console.log(
`${chalk.green("✓")} ${chalk.bold(
pathJoin(".", pathRelative(process.cwd(), targetFilePath))
)} copy pasted from the Keycloakify source code into your project`
);
edit_KcApp: {
if (
pageIdOrComponent !== templateValue &&
pageIdOrComponent !== userProfileFormFieldsValue
) {
break edit_KcApp;
}
const kcAppTsxPath = pathJoin(themeSrcDirPath, themeType, "KcApp.tsx");
const kcAppTsxCode = fs.readFileSync(kcAppTsxPath).toString("utf8");
const modifiedKcAppTsxCode = (() => {
switch (pageIdOrComponent) {
case templateValue:
return kcAppTsxCode.replace(
`keycloakify/${themeType}/Template`,
"./Template"
);
case userProfileFormFieldsValue:
return kcAppTsxCode.replace(
`keycloakify/login/UserProfileFormFields`,
"./UserProfileFormFields"
);
}
assert<Equals<typeof pageIdOrComponent, never>>(false);
})();
if (kcAppTsxCode === modifiedKcAppTsxCode) {
console.log(
chalk.red(
"Unable to automatically update KcApp.tsx, please update it manually"
)
);
return;
}
fs.writeFileSync(kcAppTsxPath, Buffer.from(modifiedKcAppTsxCode, "utf8"));
console.log(
`${chalk.green("✓")} ${chalk.bold(
pathJoin(".", pathRelative(process.cwd(), kcAppTsxPath))
)} Updated`
);
return;
}
const userProfileFormFieldComponentName = "UserProfileFormFields";
console.log(
[
``,
`${chalk.green("✓")} ${chalk.bold(
pathJoin(".", pathRelative(process.cwd(), targetFilePath))
)} copy pasted from the Keycloakify source code into your project`,
``,
`You now need to update your page router:`,
``,
@ -127,10 +208,10 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
`// ...`,
``,
chalk.green(
`+const ${componentPageBasename.replace(
`+const ${componentBasename.replace(
/.tsx$/,
""
)} = lazy(() => import("./pages/${componentPageBasename}"));`
)} = lazy(() => import("./pages/${componentBasename}"));`
),
...[
``,
@ -143,11 +224,11 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
` {(() => {`,
` switch (kcContext.pageId) {`,
` // ...`,
`+ case "${pageId}": return (`,
`+ case "${pageIdOrComponent}": return (`,
`+ <Login`,
`+ {...{ kcContext, i18n, classes }}`,
`+ Template={Template}`,
...(!componentPageContent.includes(userProfileFormFieldComponentName)
...(!componentCode.includes(userProfileFormFieldComponentName)
? []
: [
`+ ${userProfileFormFieldComponentName}={${userProfileFormFieldComponentName}}`

View File

@ -299,6 +299,8 @@ function decodeHtmlEntities(htmlStr){
<#local out_seq += ["/*" + path?join(".") + "." + key + " excluded*/"]>
<#continue>
</#if>
USER_DEFINED_EXCLUSIONS_eKsaY4ZsZ4eMr2
<#-- https://github.com/keycloakify/keycloakify/discussions/406 -->
<#if (

View File

@ -21,6 +21,7 @@ export type BuildOptionsLike = {
urlPathname: string | undefined;
reactAppBuildDirPath: string;
assetsDirPath: string;
kcContextExclusionsFtlCode: string | undefined;
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
@ -140,6 +141,10 @@ export function generateFtlFilesCodeFactory(params: {
.replace(
"lOCALIZATION_REALM_OVERRIDES_USER_PROFILE_PROPERTY_KEY_aaGLsPgGIdeeX",
nameOfTheLocalizationRealmOverridesUserProfileProperty
)
.replace(
"USER_DEFINED_EXCLUSIONS_eKsaY4ZsZ4eMr2",
buildOptions.kcContextExclusionsFtlCode ?? ""
);
const ftlObjectToJsCodeDeclaringAnObjectPlaceholder =
'{ "x": "vIdLqMeOed9sdLdIdOxdK0d" }';

View File

@ -10,7 +10,7 @@ import {
import { downloadKeycloakDefaultTheme } from "../../shared/downloadKeycloakDefaultTheme";
import { transformCodebase } from "../../tools/transformCodebase";
type BuildOptionsLike = {
export type BuildOptionsLike = {
cacheDirPath: string;
npmWorkspaceRootDirPath: string;
keycloakifyBuildDirPath: string;

View File

@ -3,7 +3,10 @@ import * as fs from "fs";
import { join as pathJoin, resolve as pathResolve } from "path";
import { replaceImportsInJsCode } from "../replacers/replaceImportsInJsCode";
import { replaceImportsInCssCode } from "../replacers/replaceImportsInCssCode";
import { generateFtlFilesCodeFactory } from "../generateFtl";
import {
generateFtlFilesCodeFactory,
type BuildOptionsLike as BuildOptionsLike_kcContextExclusionsFtlCode
} from "../generateFtl";
import {
type ThemeType,
lastKeycloakVersionWithAccountV1,
@ -16,11 +19,17 @@ import {
import { isInside } from "../../tools/isInside";
import type { BuildOptions } from "../../shared/buildOptions";
import { assert, type Equals } from "tsafe/assert";
import { downloadKeycloakStaticResources } from "../../shared/downloadKeycloakStaticResources";
import {
downloadKeycloakStaticResources,
type BuildOptionsLike as BuildOptionsLike_downloadKeycloakStaticResources
} from "../../shared/downloadKeycloakStaticResources";
import { readFieldNameUsage } from "./readFieldNameUsage";
import { readExtraPagesNames } from "./readExtraPageNames";
import { generateMessageProperties } from "./generateMessageProperties";
import { bringInAccountV1 } from "./bringInAccountV1";
import {
bringInAccountV1,
type BuildOptionsLike as BuildOptionsLike_bringInAccountV1
} from "./bringInAccountV1";
import { getThemeSrcDirPath } from "../../shared/getThemeSrcDirPath";
import { rmSync } from "../../tools/fs.rmSync";
import { readThisNpmPackageVersion } from "../../tools/readThisNpmPackageVersion";
@ -30,19 +39,18 @@ import {
} from "../../shared/metaInfKeycloakThemes";
import { objectEntries } from "tsafe/objectEntries";
export type BuildOptionsLike = {
bundler: "vite" | "webpack";
extraThemeProperties: string[] | undefined;
themeVersion: string;
loginThemeResourcesFromKeycloakVersion: string;
reactAppBuildDirPath: string;
cacheDirPath: string;
assetsDirPath: string;
urlPathname: string | undefined;
npmWorkspaceRootDirPath: string;
reactAppRootDirPath: string;
keycloakifyBuildDirPath: string;
};
export type BuildOptionsLike = BuildOptionsLike_kcContextExclusionsFtlCode &
BuildOptionsLike_downloadKeycloakStaticResources &
BuildOptionsLike_bringInAccountV1 & {
bundler: "vite" | "webpack";
extraThemeProperties: string[] | undefined;
loginThemeResourcesFromKeycloakVersion: string;
reactAppBuildDirPath: string;
assetsDirPath: string;
urlPathname: string | undefined;
reactAppRootDirPath: string;
keycloakifyBuildDirPath: string;
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();

View File

@ -21,7 +21,7 @@ export function readExtraPagesNames(params: {
}).filter(filePath => /\.(ts|tsx|js|jsx)$/.test(filePath));
const candidateFilePaths = filePaths.filter(filePath =>
/kcContext\.[^.]+$/.test(filePath)
/[kK]cContext\.[^.]+$/.test(filePath)
);
if (candidateFilePaths.length === 0) {

View File

@ -162,6 +162,20 @@ program
}
});
program
.command({
name: "add-story",
description: "Add *.stories.tsx file for a specific page to in your Storybook."
})
.task({
skip,
handler: async cliCommandOptions => {
const { command } = await import("./add-story");
await command({ cliCommandOptions });
}
});
program
.command({
name: "initialize-email-theme",

View File

@ -30,6 +30,7 @@ export type BuildOptions = {
urlPathname: string | undefined;
assetsDirPath: string;
npmWorkspaceRootDirPath: string;
kcContextExclusionsFtlCode: string | undefined;
};
export type UserProvidedBuildOptions = {
@ -39,6 +40,7 @@ export type UserProvidedBuildOptions = {
loginThemeResourcesFromKeycloakVersion?: string;
keycloakifyBuildDirPath?: string;
themeName?: string | string[];
kcContextExclusionsFtlCode?: string;
};
export type ResolvedViteConfig = {
@ -302,6 +304,7 @@ export function readBuildOptions(params: {
return pathJoin(reactAppBuildDirPath, resolvedViteConfig.assetsDir);
})(),
npmWorkspaceRootDirPath
npmWorkspaceRootDirPath,
kcContextExclusionsFtlCode: userProvidedBuildOptions.kcContextExclusionsFtlCode
};
}

View File

@ -1,10 +1,11 @@
{
"extends": "../../tsproject.json",
"compilerOptions": {
"module": "CommonJS",
"target": "ES5",
"module": "ES2020",
"target": "ES2017",
"esModuleInterop": true,
"lib": ["es2015", "DOM", "ES2019.Object"],
"lib": ["es2015", "ES2019.Object"],
"moduleResolution": "node",
"outDir": "../../dist/bin",
"rootDir": "."
}

View File

@ -1,9 +1,9 @@
import { lazy, Suspense } from "react";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { assert, type Equals } from "tsafe/assert";
import type { I18n } from "./i18n";
import type { KcContext } from "./kcContext";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "./KcContext";
import type { I18n } from "./i18n";
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
const Login = lazy(() => import("keycloakify/login/pages/Login"));

View File

@ -2,6 +2,7 @@ export type {
ExtendKcContext,
KcContext,
Attribute,
PasswordPolicies
PasswordPolicies,
Validators
} from "./KcContext";
export { createGetKcContextMock } from "./getKcContextMock";

View File

@ -1,4 +1,4 @@
import "minimal-polyfills/Object.fromEntries";
import "keycloakify/tools/Object.fromEntries";
import type { KcContext, Attribute } from "./KcContext";
import {
resources_common,
@ -87,12 +87,9 @@ export const kcContextCommonMock: KcContext.Common = {
loginAction: "#",
resourcesPath,
resourcesCommonPath: `${resourcesPath}/${resources_common}`,
loginRestartFlowUrl:
"/auth/realms/myrealm/login-actions/restart?client_id=account&tab_id=HoAx28ja4xg",
loginUrl:
"/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg",
ssoLoginInOtherTabsUrl:
"/auth/realms/myrealm/login-actions/switch?client_id=account&tab_id=HoAx28ja4xg"
loginRestartFlowUrl: "#",
loginUrl: "#",
ssoLoginInOtherTabsUrl: "#"
},
realm: {
name: "myrealm",
@ -160,13 +157,10 @@ export const kcContextCommonMock: KcContext.Common = {
const loginUrl = {
...kcContextCommonMock.url,
loginResetCredentialsUrl:
"/auth/realms/myrealm/login-actions/reset-credentials?client_id=account&tab_id=HoAx28ja4xg",
registrationUrl:
"/auth/realms/myrealm/login-actions/registration?client_id=account&tab_id=HoAx28ja4xg",
oauth2DeviceVerificationAction: "/auth/realms/myrealm/device",
oauthAction:
"/auth/realms/myrealm/login-actions/consent?client_id=account&tab_id=HoAx28ja4xg"
loginResetCredentialsUrl: "#",
registrationUrl: "#",
oauth2DeviceVerificationAction: "#",
oauthAction: "#"
};
export const kcContextMocks = [
@ -194,8 +188,7 @@ export const kcContextMocks = [
...kcContextCommonMock,
url: {
...loginUrl,
registrationAction:
"http://localhost:8080/auth/realms/myrealm/login-actions/registration?session_code=gwZdUeO7pbYpFTRxiIxRg_QtzMbtFTKrNu6XW_f8asM&execution=12146ce0-b139-4bbd-b25b-0eccfee6577e&client_id=account&tab_id=uS8lYfebLa0"
registrationAction: "#"
},
isAppInitiatedAction: false,
passwordRequired: true,
@ -445,7 +438,7 @@ export const kcContextMocks = [
...kcContextCommonMock,
pageId: "saml-post-form.ftl",
samlPost: {
url: ""
url: "#"
}
}),
id<KcContext.LoginPageExpired>({

View File

@ -1,12 +1,12 @@
import { useEffect } from "react";
import { assert } from "keycloakify/tools/assert";
import { clsx } from "keycloakify/tools/clsx";
import { type TemplateProps } from "keycloakify/login/TemplateProps";
import type { TemplateProps } from "keycloakify/login/TemplateProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
import { useInsertLinkTags } from "keycloakify/tools/useInsertLinkTags";
import { useSetClassName } from "keycloakify/tools/useSetClassName";
import type { KcContext } from "./kcContext";
import type { KcContext } from "./KcContext";
import type { I18n } from "./i18n";
export default function Template(props: TemplateProps<KcContext, I18n>) {
@ -129,11 +129,10 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
<div className={getClassName("kcLocaleMainClass")} id="kc-locale">
<div id="kc-locale-wrapper" className={getClassName("kcLocaleWrapperClass")}>
<div id="kc-locale-dropdown" className={clsx("menu-button-links", getClassName("kcLocaleDropDownClass"))}>
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<button
tabIndex={1}
id="kc-current-locale-link"
aria-label={msgStr("languages" as any)}
aria-label={msgStr("languages")}
aria-haspopup="true"
aria-expanded="false"
aria-controls="language-switch1"
@ -247,7 +246,6 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
<div className={getClassName("kcFormGroupClass")}>
<div className={getClassName("kcFormGroupClass")}>
<input type="hidden" name="tryAnotherWay" value="on" />
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a
href="#"
id="try-another-way"

View File

@ -1,5 +1,5 @@
import type { ReactNode } from "react";
import type { KcContext } from "./kcContext";
import type { KcContext } from "./KcContext";
import type { I18n } from "./i18n";
export type TemplateProps<

View File

@ -1,4 +1,5 @@
import { useEffect, useReducer, Fragment } from "react";
import { assert } from "tsafe/assert";
import type { ClassKey } from "keycloakify/login/TemplateProps";
import {
useUserProfileForm,
@ -7,8 +8,7 @@ import {
type FormAction,
type FormFieldError
} from "keycloakify/login/lib/useUserProfileForm";
import type { Attribute } from "keycloakify/login/kcContext/KcContext";
import { assert } from "tsafe/assert";
import type { Attribute } from "keycloakify/login/KcContext";
import type { I18n } from "./i18n";
export type UserProfileFormFieldsProps = {

View File

@ -1,9 +1,10 @@
import "minimal-polyfills/Object.fromEntries";
import "keycloakify/tools/Object.fromEntries";
import { useEffect, useState, useRef } from "react";
import { assert } from "tsafe/assert";
import fallbackMessages from "./baseMessages/en";
import { getMessages } from "./baseMessages";
import { assert } from "tsafe/assert";
import type { KcContext } from "../kcContext/KcContext";
import type { KcContext } from "../KcContext";
import { Reflect } from "tsafe/Reflect";
export const fallbackLanguageTag = "en";
@ -17,7 +18,7 @@ export type KcContextLike = {
assert<KcContext extends KcContextLike ? true : false>();
export type MessageKey = keyof typeof fallbackMessages | keyof (typeof keycloakifyExtraMessages)[typeof fallbackLanguageTag];
export type MessageKey = keyof typeof fallbackMessages;
export type GenericI18n<MessageKey extends string> = {
/**
@ -107,12 +108,10 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
...createI18nTranslationFunctions({
fallbackMessages: {
...fallbackMessages,
...(keycloakifyExtraMessages[fallbackLanguageTag] ?? {}),
...(extraMessages[fallbackLanguageTag] ?? {})
} as any,
messages: {
...(await getMessages(currentLanguageTag)),
...((keycloakifyExtraMessages as any)[currentLanguageTag] ?? {}),
...(extraMessages[currentLanguageTag] ?? {})
} as any,
__localizationRealmOverridesUserProfile: kcContext.__localizationRealmOverridesUserProfile
@ -139,7 +138,10 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
return i18n ?? null;
}
return { useI18n };
return {
useI18n,
ofTypeI18n: Reflect<GenericI18n<MessageKey | ExtraMessageKey>>()
};
}
function createI18nTranslationFunctions<MessageKey extends string>(params: {
@ -258,32 +260,3 @@ function createI18nTranslationFunctions<MessageKey extends string>(params: {
}) as string
};
}
const keycloakifyExtraMessages = {
en: {
shouldBeEqual: "{0} should be equal to {1}",
shouldBeDifferent: "{0} should be different to {1}",
shouldMatchPattern: "Pattern should match: `/{0}/`",
mustBeAnInteger: "Must be an integer",
notAValidOption: "Not a valid option",
selectAnOption: "Select an option",
remove: "Remove",
addValue: "Add value"
},
fr: {
/* spell-checker: disable */
shouldBeEqual: "{0} doit être égal à {1}",
shouldBeDifferent: "{0} doit être différent de {1}",
shouldMatchPattern: "Dois respecter le schéma: `/{0}/`",
mustBeAnInteger: "Doit être un nombre entier",
notAValidOption: "N'est pas une option valide",
logoutConfirmTitle: "Déconnexion",
logoutConfirmHeader: "Êtes-vous sûr(e) de vouloir vous déconnecter ?",
doLogout: "Se déconnecter",
selectAnOption: "Sélectionner une option",
remove: "Supprimer",
addValue: "Ajouter une valeur"
/* spell-checker: enable */
}
};

View File

@ -1 +1,2 @@
export type { I18n } from "./i18n";
export type { I18n, MessageKey } from "./i18n";
export { createUseI18n, fallbackLanguageTag } from "./i18n";

View File

@ -1,14 +1,4 @@
import Fallback from "keycloakify/login/Fallback";
export default Fallback;
export { useDownloadTerms } from "keycloakify/login/lib/useDownloadTerms";
export { createUseI18n } from "keycloakify/login/i18n/i18n";
export type {
ExtendKcContext,
Attribute,
PasswordPolicies
} from "keycloakify/login/kcContext";
export { createGetKcContextMock } from "keycloakify/login/kcContext";
export type { ExtendKcContext, Attribute } from "keycloakify/login/KcContext";
export type { PageProps } from "keycloakify/login/pages/PageProps";
export { useDownloadTerms } from "keycloakify/login/lib/useDownloadTerms";
export { createUseI18n } from "keycloakify/login/i18n";

View File

@ -1,11 +1,11 @@
import { fallbackLanguageTag } from "keycloakify/login/i18n/i18n";
import { fallbackLanguageTag } from "keycloakify/login/i18n";
import { assert } from "tsafe/assert";
import {
createStatefulObservable,
useRerenderOnChange
} from "keycloakify/tools/StatefulObservable";
import { KcContext } from "../kcContext";
import { useOnFistMount } from "keycloakify/tools/useOnFirstMount";
import { KcContext } from "../KcContext";
const obsTermsMarkdown = createStatefulObservable<string | undefined>(() => undefined);

View File

@ -1,15 +1,14 @@
import "keycloakify/tools/Array.prototype.every";
import { useMemo, useReducer, useEffect, Fragment, type Dispatch } from "react";
import { assert, type Equals } from "tsafe/assert";
import { id } from "tsafe/id";
import type { MessageKey } from "keycloakify/login/i18n/i18n";
import type { Attribute, Validators } from "keycloakify/login/kcContext/KcContext";
import { structuredCloneButFunctions } from "keycloakify/tools/structuredCloneButFunctions";
import { useConstCallback } from "keycloakify/tools/useConstCallback";
import { emailRegexp } from "keycloakify/tools/emailRegExp";
import type { KcContext, PasswordPolicies } from "keycloakify/login/kcContext/KcContext";
import { assert, type Equals } from "tsafe/assert";
import { formatNumber } from "keycloakify/tools/formatNumber";
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
import { structuredCloneButFunctions } from "keycloakify/tools/structuredCloneButFunctions";
import type { KcContext, PasswordPolicies, Attribute, Validators } from "keycloakify/login/KcContext";
import type { MessageKey } from "keycloakify/login/i18n";
import type { I18n } from "../i18n";
export type FormFieldError = {

View File

@ -1,6 +1,6 @@
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function Code(props: PageProps<Extract<KcContext, { pageId: "code.ftl" }>, I18n>) {

View File

@ -1,7 +1,7 @@
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function DeleteAccountConfirm(props: PageProps<Extract<KcContext, { pageId: "delete-account-confirm.ftl" }>, I18n>) {

View File

@ -1,8 +1,8 @@
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../kcContext";
import type { I18n } from "../i18n";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function DeleteCredential(props: PageProps<Extract<KcContext, { pageId: "delete-credential.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;

View File

@ -1,5 +1,5 @@
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../kcContext";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function Error(props: PageProps<Extract<KcContext, { pageId: "error.ftl" }>, I18n>) {

View File

@ -1,6 +1,6 @@
import { useEffect } from "react";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../kcContext";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function FrontchannelLogout(props: PageProps<Extract<KcContext, { pageId: "frontchannel-logout.ftl" }>, I18n>) {

View File

@ -1,10 +1,10 @@
import { useState } from "react";
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
import type { KcContext } from "../kcContext";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
type IdpReviewUserProfileProps = PageProps<Extract<KcContext, { pageId: "idp-review-user-profile.ftl" }>, I18n> & {

View File

@ -1,6 +1,6 @@
import { assert } from "keycloakify/tools/assert";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../kcContext";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function Info(props: PageProps<Extract<KcContext, { pageId: "info.ftl" }>, I18n>) {

View File

@ -3,7 +3,7 @@ import { assert } from "tsafe/assert";
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function Login(props: PageProps<Extract<KcContext, { pageId: "login.ftl" }>, I18n>) {

View File

@ -1,7 +1,7 @@
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pageId: "login-config-totp.ftl" }>, I18n>) {

View File

@ -1,7 +1,7 @@
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function LoginIdpLinkConfirm(props: PageProps<Extract<KcContext, { pageId: "login-idp-link-confirm.ftl" }>, I18n>) {

View File

@ -1,6 +1,6 @@
import type { KcContext } from "keycloakify/login/kcContext";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { I18n } from "keycloakify/login/i18n";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function LoginIdpLinkEmail(props: PageProps<Extract<KcContext, { pageId: "login-idp-link-email.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;

View File

@ -1,8 +1,8 @@
import { clsx } from "keycloakify/tools/clsx";
import { I18n } from "../i18n";
import { KcContext } from "../kcContext";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import { PageProps } from "./PageProps";
import { PageProps } from "keycloakify/login/pages/PageProps";
import { I18n } from "../i18n";
import { KcContext } from "../KcContext";
export default function LoginOauth2DeviceVerifyUserCode(
props: PageProps<Extract<KcContext, { pageId: "login-oauth2-device-verify-user-code.ftl" }>, I18n>

View File

@ -1,8 +1,8 @@
import { clsx } from "keycloakify/tools/clsx";
import { PageProps } from "./PageProps";
import { KcContext } from "../kcContext";
import { I18n } from "../i18n";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import { PageProps } from "keycloakify/login/pages/PageProps";
import { KcContext } from "../KcContext";
import { I18n } from "../i18n";
export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pageId: "login-oauth-grant.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, classes, Template } = props;

View File

@ -1,8 +1,8 @@
import { Fragment } from "react";
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function LoginOtp(props: PageProps<Extract<KcContext, { pageId: "login-otp.ftl" }>, I18n>) {

View File

@ -1,5 +1,5 @@
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../kcContext";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function LoginPageExpired(props: PageProps<Extract<KcContext, { pageId: "login-page-expired.ftl" }>, I18n>) {

View File

@ -1,9 +1,9 @@
import { useState, useEffect, useReducer } from "react";
import { clsx } from "keycloakify/tools/clsx";
import { assert } from "tsafe/assert";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function LoginPassword(props: PageProps<Extract<KcContext, { pageId: "login-password.ftl" }>, I18n>) {

View File

@ -1,9 +1,9 @@
import { useEffect } from "react";
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
import type { KcContext } from "../kcContext";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<KcContext, { pageId: "login-recovery-authn-code-config.ftl" }>, I18n>) {

View File

@ -1,7 +1,7 @@
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function LoginRecoveryAuthnCodeInput(props: PageProps<Extract<KcContext, { pageId: "login-recovery-authn-code-input.ftl" }>, I18n>) {

View File

@ -1,8 +1,8 @@
import { Fragment } from "react";
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function LoginResetOtp(props: PageProps<Extract<KcContext, { pageId: "login-reset-otp.ftl" }>, I18n>) {

View File

@ -1,7 +1,7 @@
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function LoginResetPassword(props: PageProps<Extract<KcContext, { pageId: "login-reset-password.ftl" }>, I18n>) {

View File

@ -1,9 +1,9 @@
import { useEffect, useReducer } from "react";
import { clsx } from "keycloakify/tools/clsx";
import { assert } from "tsafe/assert";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function LoginUpdatePassword(props: PageProps<Extract<KcContext, { pageId: "login-update-password.ftl" }>, I18n>) {

View File

@ -1,11 +1,11 @@
import { useState } from "react";
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { I18n } from "../i18n";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
type LoginUpdateProfileProps = PageProps<Extract<KcContext, { pageId: "login-update-profile.ftl" }>, I18n> & {
UserProfileFormFields: LazyOrNot<(props: UserProfileFormFieldsProps) => JSX.Element>;

View File

@ -1,8 +1,8 @@
import { useState } from "react";
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function LoginUsername(props: PageProps<Extract<KcContext, { pageId: "login-username.ftl" }>, I18n>) {

View File

@ -1,5 +1,5 @@
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../kcContext";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function LoginVerifyEmail(props: PageProps<Extract<KcContext, { pageId: "login-verify-email.ftl" }>, I18n>) {

View File

@ -1,7 +1,7 @@
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function LoginX509Info(props: PageProps<Extract<KcContext, { pageId: "login-x509-info.ftl" }>, I18n>) {

View File

@ -1,7 +1,7 @@
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function LogoutConfirm(props: PageProps<Extract<KcContext, { pageId: "logout-confirm.ftl" }>, I18n>) {

View File

@ -1,7 +1,7 @@
import type { I18n } from "keycloakify/login/i18n";
import { type TemplateProps, type ClassKey } from "keycloakify/login/TemplateProps";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import type { KcContext } from "keycloakify/account/kcContext";
import type { KcContext } from "keycloakify/account/KcContext";
export type PageProps<NarowedKcContext = KcContext, I18nExtended extends I18n = I18n> = {
Template: LazyOrNot<(props: TemplateProps<any, any>) => JSX.Element | null>;

View File

@ -1,12 +1,12 @@
import { useState } from "react";
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import { Markdown } from "keycloakify/tools/Markdown";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import { useTermsMarkdown } from "keycloakify/login/lib/useDownloadTerms";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
import { Markdown } from "keycloakify/tools/Markdown";
import type { KcContext } from "../kcContext";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
type RegisterProps = PageProps<Extract<KcContext, { pageId: "register.ftl" }>, I18n> & {

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../kcContext";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function SamlPostForm(props: PageProps<Extract<KcContext, { pageId: "saml-post-form.ftl" }>, I18n>) {

View File

@ -1,8 +1,8 @@
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "keycloakify/login/kcContext";
import type { I18n } from "keycloakify/login/i18n";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function SelectAuthenticator(props: PageProps<Extract<KcContext, { pageId: "select-authenticator.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;

View File

@ -1,9 +1,9 @@
import { clsx } from "keycloakify/tools/clsx";
import { Markdown } from "keycloakify/tools/Markdown";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import { useTermsMarkdown } from "keycloakify/login/lib/useDownloadTerms";
import type { KcContext } from "../kcContext";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function Terms(props: PageProps<Extract<KcContext, { pageId: "terms.ftl" }>, I18n>) {

View File

@ -1,10 +1,10 @@
import { useState } from "react";
import { clsx } from "keycloakify/tools/clsx";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { UserProfileFormFieldsProps } from "keycloakify/login/UserProfileFormFields";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
type UpdateEmailProps = PageProps<Extract<KcContext, { pageId: "update-email.ftl" }>, I18n> & {

View File

@ -1,10 +1,10 @@
import { useEffect, Fragment } from "react";
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import { assert } from "tsafe/assert";
import { clsx } from "keycloakify/tools/clsx";
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
import type { KcContext } from "../kcContext";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext, { pageId: "webauthn-authenticate.ftl" }>, I18n>) {

View File

@ -1,7 +1,7 @@
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { clsx } from "keycloakify/tools/clsx";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import type { KcContext } from "../kcContext";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function WebauthnError(props: PageProps<Extract<KcContext, { pageId: "webauthn-error.ftl" }>, I18n>) {

View File

@ -1,10 +1,10 @@
import { useEffect } from "react";
import { clsx } from "keycloakify/tools/clsx";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import { assert } from "tsafe/assert";
import { clsx } from "keycloakify/tools/clsx";
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
import type { KcContext } from "../kcContext";
import type { PageProps } from "keycloakify/login/pages/PageProps";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";
export default function WebauthnRegister(props: PageProps<Extract<KcContext, { pageId: "webauthn-register.ftl" }>, I18n>) {

View File

@ -0,0 +1,23 @@
if (!(Object as any).fromEntries) {
Object.defineProperty(Object, "fromEntries", {
value: function (entries: any) {
if (!entries || !entries[Symbol.iterator]) {
throw new Error(
"Object.fromEntries() requires a single iterable argument"
);
}
const o: any = {};
Object.keys(entries).forEach(key => {
const [k, v] = entries[key];
o[k] = v;
});
return o;
}
});
}
export {};

View File

@ -1,4 +1,4 @@
import "minimal-polyfills/Object.fromEntries";
import "./Object.fromEntries";
/**
* Functionally equivalent to structuredClone but

View File

@ -1,5 +1,5 @@
import { useEffect } from "react";
import { useConst } from "powerhooks/useConst";
import { useConst } from "./useConst";
import { id } from "tsafe/id";
/** Callback is guaranteed to be call only once per component mount event in strict mode */

View File

@ -1,6 +1,6 @@
import React from "react";
import Fallback from "../../dist/account";
import type { KcContext } from "./kcContext";
import Fallback from "../../dist/account/Fallback";
import type { KcContext } from "./KcContext";
import { useI18n } from "./i18n";
import Template from "../../dist/account/Template";

View File

@ -1,8 +1,17 @@
import React from "react";
import type { KcContext } from "./kcContext";
import { getKcContextMock } from "./kcContextMock";
import type { KcContext } from "./KcContext";
import KcApp from "./KcApp";
import type { DeepPartial } from "../../dist/tools/DeepPartial";
import { createGetKcContextMock } from "../../dist/account/KcContext";
import type { KcContextExtraProperties, KcContextExtraPropertiesPerPage } from "./KcContext";
const kcContextExtraProperties: KcContextExtraProperties = {};
const kcContextExtraPropertiesPerPage: KcContextExtraPropertiesPerPage = {};
export const { getKcContextMock } = createGetKcContextMock({
kcContextExtraProperties,
kcContextExtraPropertiesPerPage
});
export function createPageStory<PageId extends KcContext["pageId"]>(params: { pageId: PageId }) {
const { pageId } = params;
@ -24,12 +33,3 @@ export function createPageStory<PageId extends KcContext["pageId"]>(params: { pa
return { PageStory };
}
export const parameters = {
viewMode: "story",
previewTabs: {
"storybook/docs/panel": {
hidden: true
}
}
};

View File

@ -1,5 +1,5 @@
import { createUseI18n } from "../../dist/account";
export const { useI18n } = createUseI18n({});
export const { useI18n, ofTypeI18n } = createUseI18n({});
export type I18n = NonNullable<ReturnType<typeof useI18n>>;
export type I18n = typeof ofTypeI18n;

View File

@ -1,13 +0,0 @@
import { createGetKcContextMock } from "../../dist/account";
import type {
KcContextExtraProperties,
KcContextExtraPropertiesPerPage
} from "./kcContext";
const kcContextExtraProperties: KcContextExtraProperties = {};
const kcContextExtraPropertiesPerPage: KcContextExtraPropertiesPerPage = {};
export const { getKcContextMock } = createGetKcContextMock({
kcContextExtraProperties,
kcContextExtraPropertiesPerPage
});

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../createPageStory";
import { createPageStory } from "../PageStory";
const pageId = "account.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "account.ftl" });
const meta = {
title: `account/${pageId}`,
component: PageStory,
parameters
title: "account/account.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../createPageStory";
import { createPageStory } from "../PageStory";
const pageId = "federatedIdentity.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "federatedIdentity.ftl" });
const meta = {
title: `account/${pageId}`,
component: PageStory,
parameters
title: "account/federatedIdentity.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,13 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../createPageStory";
const pageId = "log.ftl";
import { createPageStory } from "../PageStory";
const { PageStory } = createPageStory({
pageId
pageId: "log.ftl"
});
const meta = {
title: `account/${pageId}`,
title: "account/log.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../createPageStory";
import { createPageStory } from "../PageStory";
const pageId = "password.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "password.ftl" });
const meta = {
title: `account/${pageId}`,
component: PageStory,
parameters
title: "account/password.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../createPageStory";
import { createPageStory } from "../PageStory";
const pageId = "sessions.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "sessions.ftl" });
const meta = {
title: `account/${pageId}`,
component: PageStory,
parameters
title: "account/sessions.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,17 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../createPageStory";
import { createPageStory } from "../PageStory";
const pageId = "totp.ftl";
const { PageStory } = createPageStory({
pageId
});
const { PageStory } = createPageStory({ pageId: "totp.ftl" });
const meta = {
title: `account/${pageId}`,
component: PageStory,
parameters
title: "account/totp.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,6 +1,6 @@
import React from "react";
import Fallback from "../../dist/login";
import type { KcContext } from "./kcContext";
import Fallback from "../../dist/login/Fallback";
import type { KcContext } from "./KcContext";
import { useI18n } from "./i18n";
import { useDownloadTerms } from "../../dist/login/lib/useDownloadTerms";
import Template from "../../dist/login/Template";
@ -14,18 +14,19 @@ export default function KcApp(props: { kcContext: KcContext }) {
useDownloadTerms({
kcContext,
downloadTermMarkdown: async ({ currentLanguageTag }) => {
const resource = (() => {
const termsFileName = (() => {
switch (currentLanguageTag) {
case "fr":
return "/tos/tos_fr.md";
return "fr.md";
case "es":
return "/tos/tos_es.md";
return "es.md";
default:
return "/tos/tos_en.md";
return "en.md";
}
})();
const response = await fetch(resource);
const response = await fetch(`/terms/${termsFileName}`);
return response.text();
}
});

Some files were not shown because too many files have changed in this diff Show More