Skip to content

Commit 5da6300

Browse files
committed
Merge PR CorentinTh#1683: add IBAN generator tool
2 parents 738e59b + 6edea3e commit 5da6300

4 files changed

Lines changed: 217 additions & 9 deletions

File tree

components.d.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
// @ts-nocheck
44
// Generated by unplugin-vue-components
55
// Read more: https://github.com/vuejs/core/pull/3399
6+
import '@vue/runtime-core'
7+
68
export {}
79

8-
declare module 'vue' {
10+
declare module '@vue/runtime-core' {
911
export interface GlobalComponents {
1012
'404.page': typeof import('./src/pages/404.page.vue')['default']
1113
About: typeof import('./src/pages/About.vue')['default']
@@ -80,13 +82,13 @@ declare module 'vue' {
8082
FormatTransformer: typeof import('./src/components/FormatTransformer.vue')['default']
8183
GitMemo: typeof import('./src/tools/git-memo/git-memo.vue')['default']
8284
'GitMemo.content': typeof import('./src/tools/git-memo/git-memo.content.md')['default']
83-
GzipConverter: typeof import('./src/tools/gzip-converter/gzip-converter.vue')['default']
8485
HashText: typeof import('./src/tools/hash-text/hash-text.vue')['default']
8586
HmacGenerator: typeof import('./src/tools/hmac-generator/hmac-generator.vue')['default']
8687
'Home.page': typeof import('./src/pages/Home.page.vue')['default']
8788
HtmlEntities: typeof import('./src/tools/html-entities/html-entities.vue')['default']
8889
HtmlWysiwygEditor: typeof import('./src/tools/html-wysiwyg-editor/html-wysiwyg-editor.vue')['default']
8990
HttpStatusCodes: typeof import('./src/tools/http-status-codes/http-status-codes.vue')['default']
91+
IbanGenerator: typeof import('./src/tools/iban-generator/iban-generator.vue')['default']
9092
IbanValidatorAndParser: typeof import('./src/tools/iban-validator-and-parser/iban-validator-and-parser.vue')['default']
9193
'IconMdi:brushVariant': typeof import('~icons/mdi/brush-variant')['default']
9294
'IconMdi:kettleSteamOutline': typeof import('~icons/mdi/kettle-steam-outline')['default']
@@ -97,6 +99,7 @@ declare module 'vue' {
9799
IconMdiEye: typeof import('~icons/mdi/eye')['default']
98100
IconMdiEyeOff: typeof import('~icons/mdi/eye-off')['default']
99101
IconMdiHeart: typeof import('~icons/mdi/heart')['default']
102+
IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
100103
IconMdiSearch: typeof import('~icons/mdi/search')['default']
101104
IconMdiTranslate: typeof import('~icons/mdi/translate')['default']
102105
IconMdiTriangleDown: typeof import('~icons/mdi/triangle-down')['default']
@@ -128,21 +131,32 @@ declare module 'vue' {
128131
MenuLayout: typeof import('./src/components/MenuLayout.vue')['default']
129132
MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default']
130133
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
134+
NAlert: typeof import('naive-ui')['NAlert']
131135
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
132-
NCheckbox: typeof import('naive-ui')['NCheckbox']
133136
NCode: typeof import('naive-ui')['NCode']
134137
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
138+
NColorPicker: typeof import('naive-ui')['NColorPicker']
135139
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
136140
NDivider: typeof import('naive-ui')['NDivider']
137141
NEllipsis: typeof import('naive-ui')['NEllipsis']
142+
NForm: typeof import('naive-ui')['NForm']
143+
NFormItem: typeof import('naive-ui')['NFormItem']
144+
NGi: typeof import('naive-ui')['NGi']
145+
NGrid: typeof import('naive-ui')['NGrid']
138146
NH1: typeof import('naive-ui')['NH1']
139147
NH3: typeof import('naive-ui')['NH3']
140148
NIcon: typeof import('naive-ui')['NIcon']
149+
NImage: typeof import('naive-ui')['NImage']
150+
NInputGroup: typeof import('naive-ui')['NInputGroup']
151+
NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel']
152+
NInputNumber: typeof import('naive-ui')['NInputNumber']
141153
NLayout: typeof import('naive-ui')['NLayout']
142154
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
143155
NMenu: typeof import('naive-ui')['NMenu']
156+
NProgress: typeof import('naive-ui')['NProgress']
144157
NScrollbar: typeof import('naive-ui')['NScrollbar']
145-
NSpace: typeof import('naive-ui')['NSpace']
158+
NStatistic: typeof import('naive-ui')['NStatistic']
159+
NSwitch: typeof import('naive-ui')['NSwitch']
146160
NTable: typeof import('naive-ui')['NTable']
147161
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
148162
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
<script setup lang="ts">
2+
import { ref, watch } from 'vue';
3+
import { random } from '@/utils/random';
4+
5+
const countries = [
6+
{ code: 'AL', name: 'Albania', bankCodeLength: 8, accountNumberLength: 16, prefix: '' },
7+
{ code: 'AD', name: 'Andorra', bankCodeLength: 4, accountNumberLength: 12, prefix: '' },
8+
{ code: 'AT', name: 'Austria', bankCodeLength: 5, accountNumberLength: 11, prefix: '' },
9+
{ code: 'AZ', name: 'Azerbaijan', bankCodeLength: 4, accountNumberLength: 20, prefix: '' },
10+
{ code: 'BH', name: 'Bahrain', bankCodeLength: 4, accountNumberLength: 14, prefix: '' },
11+
{ code: 'BE', name: 'Belgium', bankCodeLength: 3, accountNumberLength: 9, prefix: '' },
12+
{ code: 'BA', name: 'Bosnia and Herzegovina', bankCodeLength: 3, accountNumberLength: 13, prefix: '' },
13+
{ code: 'BR', name: 'Brazil', bankCodeLength: 8, accountNumberLength: 13, prefix: '' },
14+
{ code: 'BG', name: 'Bulgaria', bankCodeLength: 4, accountNumberLength: 10, prefix: '' },
15+
{ code: 'CR', name: 'Costa Rica', bankCodeLength: 3, accountNumberLength: 14, prefix: '' },
16+
{ code: 'HR', name: 'Croatia', bankCodeLength: 7, accountNumberLength: 10, prefix: '' },
17+
{ code: 'CY', name: 'Cyprus', bankCodeLength: 3, accountNumberLength: 16, prefix: '' },
18+
{ code: 'CZ', name: 'Czech Republic', bankCodeLength: 4, accountNumberLength: 16, prefix: '' },
19+
{ code: 'DK', name: 'Denmark', bankCodeLength: 4, accountNumberLength: 10, prefix: '' },
20+
{ code: 'DO', name: 'Dominican Republic', bankCodeLength: 4, accountNumberLength: 20, prefix: '' },
21+
{ code: 'EE', name: 'Estonia', bankCodeLength: 2, accountNumberLength: 16, prefix: '' },
22+
{ code: 'FO', name: 'Faroe Islands', bankCodeLength: 4, accountNumberLength: 9, prefix: '' },
23+
{ code: 'FI', name: 'Finland', bankCodeLength: 6, accountNumberLength: 7, prefix: '' },
24+
{ code: 'FR', name: 'France', bankCodeLength: 5, accountNumberLength: 11, prefix: '' },
25+
{ code: 'GE', name: 'Georgia', bankCodeLength: 2, accountNumberLength: 16, prefix: '' },
26+
{ code: 'DE', name: 'Germany', bankCodeLength: 8, accountNumberLength: 10, prefix: '' },
27+
{ code: 'GI', name: 'Gibraltar', bankCodeLength: 4, accountNumberLength: 15, prefix: '' },
28+
{ code: 'GR', name: 'Greece', bankCodeLength: 3, accountNumberLength: 16, prefix: '' },
29+
{ code: 'GL', name: 'Greenland', bankCodeLength: 4, accountNumberLength: 9, prefix: '' },
30+
{ code: 'GT', name: 'Guatemala', bankCodeLength: 4, accountNumberLength: 20, prefix: '' },
31+
{ code: 'HU', name: 'Hungary', bankCodeLength: 3, accountNumberLength: 17, prefix: '' },
32+
{ code: 'IS', name: 'Iceland', bankCodeLength: 4, accountNumberLength: 18, prefix: '' },
33+
{ code: 'IE', name: 'Ireland', bankCodeLength: 4, accountNumberLength: 10, prefix: '' },
34+
{ code: 'IL', name: 'Israel', bankCodeLength: 3, accountNumberLength: 19, prefix: '' },
35+
{ code: 'IT', name: 'Italy', bankCodeLength: 5, accountNumberLength: 12, prefix: '' },
36+
{ code: 'JO', name: 'Jordan', bankCodeLength: 4, accountNumberLength: 18, prefix: '' },
37+
{ code: 'KZ', name: 'Kazakhstan', bankCodeLength: 3, accountNumberLength: 13, prefix: '' },
38+
{ code: 'XK', name: 'Kosovo', bankCodeLength: 4, accountNumberLength: 12, prefix: '' },
39+
{ code: 'KW', name: 'Kuwait', bankCodeLength: 4, accountNumberLength: 22, prefix: '' },
40+
{ code: 'LV', name: 'Latvia', bankCodeLength: 4, accountNumberLength: 13, prefix: '' },
41+
{ code: 'LB', name: 'Lebanon', bankCodeLength: 4, accountNumberLength: 20, prefix: '' },
42+
{ code: 'LI', name: 'Liechtenstein', bankCodeLength: 5, accountNumberLength: 12, prefix: '' },
43+
{ code: 'LT', name: 'Lithuania', bankCodeLength: 5, accountNumberLength: 11, prefix: '' },
44+
{ code: 'LU', name: 'Luxembourg', bankCodeLength: 3, accountNumberLength: 13, prefix: '' },
45+
{ code: 'MK', name: 'North Macedonia', bankCodeLength: 3, accountNumberLength: 10, prefix: '' },
46+
{ code: 'MT', name: 'Malta', bankCodeLength: 4, accountNumberLength: 18, prefix: '' },
47+
{ code: 'MR', name: 'Mauritania', bankCodeLength: 5, accountNumberLength: 15, prefix: '' },
48+
{ code: 'MU', name: 'Mauritius', bankCodeLength: 6, accountNumberLength: 19, prefix: '' },
49+
{ code: 'MC', name: 'Monaco', bankCodeLength: 5, accountNumberLength: 11, prefix: '' },
50+
{ code: 'MD', name: 'Moldova', bankCodeLength: 2, accountNumberLength: 18, prefix: '' },
51+
{ code: 'ME', name: 'Montenegro', bankCodeLength: 3, accountNumberLength: 13, prefix: '' },
52+
{ code: 'NL', name: 'Netherlands', bankCodeLength: 4, accountNumberLength: 10, prefix: '' },
53+
{ code: 'NO', name: 'Norway', bankCodeLength: 4, accountNumberLength: 7, prefix: '' },
54+
{ code: 'PK', name: 'Pakistan', bankCodeLength: 4, accountNumberLength: 16, prefix: '' },
55+
{ code: 'PS', name: 'Palestine', bankCodeLength: 4, accountNumberLength: 21, prefix: '' },
56+
{ code: 'PL', name: 'Poland', bankCodeLength: 8, accountNumberLength: 16, prefix: '' },
57+
{ code: 'PT', name: 'Portugal', bankCodeLength: 4, accountNumberLength: 15, prefix: '' },
58+
{ code: 'QA', name: 'Qatar', bankCodeLength: 4, accountNumberLength: 21, prefix: '' },
59+
{ code: 'RO', name: 'Romania', bankCodeLength: 4, accountNumberLength: 16, prefix: '' },
60+
{ code: 'LC', name: 'Saint Lucia', bankCodeLength: 4, accountNumberLength: 24, prefix: '' },
61+
{ code: 'SM', name: 'San Marino', bankCodeLength: 5, accountNumberLength: 12, prefix: '' },
62+
{ code: 'ST', name: 'São Tomé and Príncipe', bankCodeLength: 8, accountNumberLength: 16, prefix: '' },
63+
{ code: 'SA', name: 'Saudi Arabia', bankCodeLength: 2, accountNumberLength: 18, prefix: '' },
64+
{ code: 'RS', name: 'Serbia', bankCodeLength: 3, accountNumberLength: 15, prefix: '' },
65+
{ code: 'SC', name: 'Seychelles', bankCodeLength: 4, accountNumberLength: 16, prefix: '' },
66+
{ code: 'SK', name: 'Slovakia', bankCodeLength: 4, accountNumberLength: 16, prefix: '' },
67+
{ code: 'SI', name: 'Slovenia', bankCodeLength: 5, accountNumberLength: 8, prefix: '' },
68+
{ code: 'ES', name: 'Spain', bankCodeLength: 8, accountNumberLength: 12, prefix: '' },
69+
{ code: 'SE', name: 'Sweden', bankCodeLength: 3, accountNumberLength: 17, prefix: '' },
70+
{ code: 'CH', name: 'Switzerland', bankCodeLength: 5, accountNumberLength: 12, prefix: '' },
71+
{ code: 'TL', name: 'Timor-Leste', bankCodeLength: 3, accountNumberLength: 14, prefix: '' },
72+
{ code: 'TN', name: 'Tunisia', bankCodeLength: 5, accountNumberLength: 15, prefix: '' },
73+
{ code: 'TR', name: 'Turkey', bankCodeLength: 5, accountNumberLength: 16, prefix: '' },
74+
{ code: 'UA', name: 'Ukraine', bankCodeLength: 6, accountNumberLength: 19, prefix: '' },
75+
{ code: 'AE', name: 'United Arab Emirates', bankCodeLength: 3, accountNumberLength: 16, prefix: '' },
76+
{ code: 'GB', name: 'United Kingdom', bankCodeLength: 4, accountNumberLength: 14, prefix: '' },
77+
{ code: 'VG', name: 'British Virgin Islands', bankCodeLength: 4, accountNumberLength: 16, prefix: '' },
78+
];
79+
80+
const selectedCountry = ref('DE');
81+
const bankCode = ref('');
82+
const accountNumber = ref('');
83+
const iban = ref('');
84+
const error = ref('');
85+
86+
function padLeft(str: string, length: number, char = '0') {
87+
return str.padStart(length, char);
88+
}
89+
90+
function randomDigits(length: number) {
91+
let digits = '';
92+
for (let i = 0; i < length; i++) {
93+
digits += Math.floor(random() * 10).toString();
94+
}
95+
return digits;
96+
}
97+
98+
function regenerate() {
99+
const country = countries.find(c => c.code === selectedCountry.value);
100+
if (!country) {
101+
return;
102+
}
103+
bankCode.value = randomDigits(country.bankCodeLength);
104+
accountNumber.value = randomDigits(country.accountNumberLength);
105+
iban.value = '';
106+
error.value = '';
107+
}
108+
109+
function generateIban() {
110+
error.value = '';
111+
const country = countries.find(c => c.code === selectedCountry.value);
112+
if (!country) {
113+
return;
114+
}
115+
regenerate();
116+
117+
const bCode = padLeft(bankCode.value, country.bankCodeLength);
118+
const aNum = padLeft(accountNumber.value, country.accountNumberLength);
119+
const bban = country.prefix + bCode + aNum;
120+
const check = calculateCheckDigits(country.code, bban);
121+
iban.value = `${country.code}${check}${bban}`;
122+
}
123+
124+
function calculateCheckDigits(countryCode: string, bban: string) {
125+
// IBAN check digits according to ISO 7064
126+
let replaced = `${bban + countryCode}00`;
127+
replaced = replaced.replace(/[A-Z]/g, c => (c.codePointAt(0)! - 55).toString());
128+
129+
const remainder = BigInt(replaced) % 97n;
130+
const check = 98n - remainder;
131+
return check.toString().padStart(2, '0');
132+
}
133+
134+
// Generate Intial IBAN and listen for country changes
135+
generateIban();
136+
137+
watch(selectedCountry, () => {
138+
generateIban();
139+
});
140+
</script>
141+
142+
<template>
143+
<div>
144+
<div mb-3 flex items-center>
145+
<span w-100px>Country </span>
146+
<c-select
147+
v-model:value="selectedCountry"
148+
searchable
149+
flex-1
150+
:options="countries.map(({ name, code }) => ({ label: name, value: code }))"
151+
data-test-id="iban-generator-country-select"
152+
/>
153+
</div>
154+
155+
<c-card
156+
align-center
157+
mb-3
158+
flex
159+
justify-center
160+
>
161+
<c-text-copyable
162+
style="text-align: center;font-family: monospace"
163+
:value="iban"
164+
placeholder="Your IBAN"
165+
/>
166+
</c-card>
167+
168+
<div flex justify-center gap-3>
169+
<c-button @click="generateIban">
170+
Refresh
171+
</c-button>
172+
</div>
173+
</div>
174+
</template>
175+
176+
<style scoped lang="less">
177+
::v-deep(.iban-display) {
178+
textarea {
179+
text-align: center;
180+
align-items: center;
181+
align-content: center;
182+
align-self: center;
183+
}
184+
}
185+
</style>

src/tools/iban-generator/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { defineTool } from '../tool';
2+
import Bank from '~icons/mdi/bank';
3+
4+
export const tool = defineTool({
5+
name: 'IBAN Generator',
6+
path: '/iban-generator',
7+
description: 'Generating valid IBANs for various countries. Useful for testing and development purposes. The generated IBANs are not linked to real bank accounts.',
8+
keywords: ['iban', 'generator'],
9+
component: () => import('./iban-generator.vue'),
10+
icon: Bank,
11+
createdAt: new Date('2025-12-04'),
12+
});

src/tools/index.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { tool as base64FileConverter } from './base64-file-converter';
22
import { tool as base64StringConverter } from './base64-string-converter';
33
import { tool as basicAuthGenerator } from './basic-auth-generator';
4-
import { tool as javascriptObfuscator } from './javascript-obfuscator';
4+
import { tool as ibanGenerator } from './iban-generator';
55
import { tool as emailNormalizer } from './email-normalizer';
66

77
import { tool as asciiTextDrawer } from './ascii-text-drawer';
@@ -88,7 +88,6 @@ import { tool as uuidGenerator } from './uuid-generator';
8888
import { tool as macAddressLookup } from './mac-address-lookup';
8989
import { tool as xmlFormatter } from './xml-formatter';
9090
import { tool as yamlViewer } from './yaml-viewer';
91-
import { tool as gzipConverter } from './gzip-converter';
9291

9392
export const toolsByCategory: ToolCategory[] = [
9493
{
@@ -118,7 +117,6 @@ export const toolsByCategory: ToolCategory[] = [
118117
xmlToJson,
119118
jsonToXml,
120119
markdownToHtml,
121-
gzipConverter,
122120
],
123121
},
124122
{
@@ -163,7 +161,6 @@ export const toolsByCategory: ToolCategory[] = [
163161
emailNormalizer,
164162
regexTester,
165163
regexMemo,
166-
javascriptObfuscator,
167164
],
168165
},
169166
{
@@ -192,7 +189,7 @@ export const toolsByCategory: ToolCategory[] = [
192189
},
193190
{
194191
name: 'Data',
195-
components: [phoneParserAndFormatter, ibanValidatorAndParser],
192+
components: [phoneParserAndFormatter, ibanValidatorAndParser, ibanGenerator],
196193
},
197194
];
198195

0 commit comments

Comments
 (0)