aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/tools/index.ts5
-rw-r--r--src/tools/phone-parser-and-formatter/index.ts25
-rw-r--r--src/tools/phone-parser-and-formatter/phone-parser-and-formatter.e2e.spec.ts11
-rw-r--r--src/tools/phone-parser-and-formatter/phone-parser-and-formatter.models.ts41
-rw-r--r--src/tools/phone-parser-and-formatter/phone-parser-and-formatter.vue107
-rw-r--r--src/utils/boolean.test.ts9
-rw-r--r--src/utils/boolean.ts6
7 files changed, 202 insertions, 2 deletions
diff --git a/src/tools/index.ts b/src/tools/index.ts
index 73f0834..bc54b4d 100644
--- a/src/tools/index.ts
+++ b/src/tools/index.ts
@@ -1,6 +1,7 @@
import { tool as base64FileConverter } from './base64-file-converter';
import { tool as base64StringConverter } from './base64-string-converter';
import { tool as basicAuthGenerator } from './basic-auth-generator';
+import { tool as phoneParserAndFormatter } from './phone-parser-and-formatter';
import { tool as jsonDiff } from './json-diff';
import { tool as ipv4RangeExpander } from './ipv4-range-expander';
import { tool as httpStatusCodes } from './http-status-codes';
@@ -128,6 +129,10 @@ export const toolsByCategory: ToolCategory[] = [
name: 'Text',
components: [loremIpsumGenerator, textStatistics],
},
+ {
+ name: 'Data',
+ components: [phoneParserAndFormatter],
+ },
];
export const tools = toolsByCategory.flatMap(({ components }) => components);
diff --git a/src/tools/phone-parser-and-formatter/index.ts b/src/tools/phone-parser-and-formatter/index.ts
new file mode 100644
index 0000000..5b19ae6
--- /dev/null
+++ b/src/tools/phone-parser-and-formatter/index.ts
@@ -0,0 +1,25 @@
+import { Phone } from '@vicons/tabler';
+import { defineTool } from '../tool';
+
+export const tool = defineTool({
+ name: 'Phone parser and formatter',
+ path: '/phone-parser-and-formatter',
+ description:
+ 'Parse, validate and format phone numbers. Get information about the phone number, like the country code, type, etc.',
+ keywords: [
+ 'phone',
+ 'parser',
+ 'formatter',
+ 'validate',
+ 'format',
+ 'number',
+ 'telephone',
+ 'mobile',
+ 'cell',
+ 'international',
+ 'national',
+ ],
+ component: () => import('./phone-parser-and-formatter.vue'),
+ icon: Phone,
+ createdAt: new Date('2023-05-01'),
+});
diff --git a/src/tools/phone-parser-and-formatter/phone-parser-and-formatter.e2e.spec.ts b/src/tools/phone-parser-and-formatter/phone-parser-and-formatter.e2e.spec.ts
new file mode 100644
index 0000000..ddf5510
--- /dev/null
+++ b/src/tools/phone-parser-and-formatter/phone-parser-and-formatter.e2e.spec.ts
@@ -0,0 +1,11 @@
+import { test, expect } from '@playwright/test';
+
+test.describe('Tool - Phone parser and formatter', () => {
+ test.beforeEach(async ({ page }) => {
+ await page.goto('/phone-parser-and-formatter');
+ });
+
+ test('Has correct title', async ({ page }) => {
+ await expect(page).toHaveTitle('Phone parser and formatter - IT Tools');
+ });
+});
diff --git a/src/tools/phone-parser-and-formatter/phone-parser-and-formatter.models.ts b/src/tools/phone-parser-and-formatter/phone-parser-and-formatter.models.ts
new file mode 100644
index 0000000..1e2a483
--- /dev/null
+++ b/src/tools/phone-parser-and-formatter/phone-parser-and-formatter.models.ts
@@ -0,0 +1,41 @@
+import type { NumberType } from 'libphonenumber-js/types';
+import lookup from 'country-code-lookup';
+
+export { formatTypeToHumanReadable, getFullCountryName, getDefaultCountryCode };
+
+const typeToLabel: Record<NonNullable<NumberType>, string> = {
+ MOBILE: 'Mobile',
+ FIXED_LINE: 'Fixed line',
+ FIXED_LINE_OR_MOBILE: 'Fixed line or mobile',
+ PERSONAL_NUMBER: 'Personal number',
+ PREMIUM_RATE: 'Premium rate',
+ SHARED_COST: 'Shared cost',
+ TOLL_FREE: 'Toll free',
+ UAN: 'Universal access number',
+ VOICEMAIL: 'Voicemail',
+ VOIP: 'VoIP',
+ PAGER: 'Pager',
+};
+
+function formatTypeToHumanReadable(type: NumberType): string | undefined {
+ if (!type) return undefined;
+
+ return typeToLabel[type];
+}
+
+function getFullCountryName(countryCode: string | undefined) {
+ if (!countryCode) return undefined;
+
+ return lookup.byIso(countryCode)?.country;
+}
+
+function getDefaultCountryCode({
+ locale = window.navigator.language,
+ defaultCode = 'FR',
+}: { locale?: string; defaultCode?: string } = {}): string {
+ const countryCode = locale.split('-')[1]?.toUpperCase();
+
+ if (!countryCode) return defaultCode;
+
+ return lookup.byIso(countryCode)?.iso2 ?? defaultCode;
+}
diff --git a/src/tools/phone-parser-and-formatter/phone-parser-and-formatter.vue b/src/tools/phone-parser-and-formatter/phone-parser-and-formatter.vue
new file mode 100644
index 0000000..d17356a
--- /dev/null
+++ b/src/tools/phone-parser-and-formatter/phone-parser-and-formatter.vue
@@ -0,0 +1,107 @@
+<template>
+ <div>
+ <n-form-item label="Default country code:">
+ <n-select v-model:value="defaultCountryCode" :options="countriesOptions" />
+ </n-form-item>
+ <n-form-item label="Phone number:" v-bind="validation.attrs">
+ <n-input v-model:value="rawPhone" placeholder="Enter a phone number" />
+ </n-form-item>
+
+ <n-table v-if="parsedDetails">
+ <tbody>
+ <tr v-for="{ label, value } in parsedDetails" :key="label">
+ <td>
+ <n-text strong>{{ label }}</n-text>
+ </td>
+ <td>
+ <span-copyable v-if="value" :value="value"></span-copyable>
+ <n-text v-else depth="3" italic>Unknown</n-text>
+ </td>
+ </tr>
+ </tbody>
+ </n-table>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { withDefaultOnError } from '@/utils/defaults';
+import { parsePhoneNumber, getCountries, getCountryCallingCode } from 'libphonenumber-js/max';
+import { booleanToHumanReadable } from '@/utils/boolean';
+import { useValidation } from '@/composable/validation';
+import lookup from 'country-code-lookup';
+import {
+ formatTypeToHumanReadable,
+ getFullCountryName,
+ getDefaultCountryCode,
+} from './phone-parser-and-formatter.models';
+
+const rawPhone = ref('');
+const defaultCountryCode = ref(getDefaultCountryCode());
+const validation = useValidation({
+ source: rawPhone,
+ rules: [
+ {
+ validator: (value) => value === '' || /^[0-9 +\-()]+$/.test(value),
+ message: 'Invalid phone number',
+ },
+ ],
+});
+
+const parsedDetails = computed(() => {
+ if (!validation.isValid) return undefined;
+
+ const parsed = withDefaultOnError(() => parsePhoneNumber(rawPhone.value, 'FR'), undefined);
+
+ if (!parsed) return undefined;
+
+ return [
+ {
+ label: 'Country',
+ value: parsed.country,
+ },
+ {
+ label: 'Country',
+ value: getFullCountryName(parsed.country),
+ },
+ {
+ label: 'Country calling code',
+ value: parsed.countryCallingCode,
+ },
+ {
+ label: 'Is valid?',
+ value: booleanToHumanReadable(parsed.isValid()),
+ },
+ {
+ label: 'Is possible?',
+ value: booleanToHumanReadable(parsed.isPossible()),
+ },
+ {
+ label: 'Type',
+ value: formatTypeToHumanReadable(parsed.getType()),
+ },
+ {
+ label: 'International format',
+ value: parsed.formatInternational(),
+ },
+ {
+ label: 'National format',
+ value: parsed.formatNational(),
+ },
+ {
+ label: 'E.164 format',
+ value: parsed.format('E.164'),
+ },
+ {
+ label: 'RFC3966 format',
+ value: parsed.format('RFC3966'),
+ },
+ ];
+});
+
+const countriesOptions = getCountries().map((code) => ({
+ label: `${lookup.byIso(code)?.country || code} (+${getCountryCallingCode(code)})`,
+ value: code,
+}));
+</script>
+
+<style lang="less" scoped></style>
diff --git a/src/utils/boolean.test.ts b/src/utils/boolean.test.ts
index 57ca10a..52bda9e 100644
--- a/src/utils/boolean.test.ts
+++ b/src/utils/boolean.test.ts
@@ -1,6 +1,6 @@
import _ from 'lodash';
import { describe, expect, it } from 'vitest';
-import { isNotThrowing } from './boolean';
+import { booleanToHumanReadable, isNotThrowing } from './boolean';
describe('boolean utils', () => {
describe('isNotThrowing', () => {
@@ -13,4 +13,11 @@ describe('boolean utils', () => {
).to.eql(false);
});
});
+
+ describe('booleanToHumanReadable', () => {
+ it('should return "Yes" if the value is true and "No" otherwise', () => {
+ expect(booleanToHumanReadable(true)).to.eql('Yes');
+ expect(booleanToHumanReadable(false)).to.eql('No');
+ });
+ });
});
diff --git a/src/utils/boolean.ts b/src/utils/boolean.ts
index 9e842ea..cf10b37 100644
--- a/src/utils/boolean.ts
+++ b/src/utils/boolean.ts
@@ -1,4 +1,4 @@
-export { isNotThrowing };
+export { isNotThrowing, booleanToHumanReadable };
function isNotThrowing(cb: () => unknown): boolean {
try {
@@ -8,3 +8,7 @@ function isNotThrowing(cb: () => unknown): boolean {
return false;
}
}
+
+function booleanToHumanReadable(value: boolean): string {
+ return value ? 'Yes' : 'No';
+}