diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/tools/index.ts | 5 | ||||
-rw-r--r-- | src/tools/phone-parser-and-formatter/index.ts | 25 | ||||
-rw-r--r-- | src/tools/phone-parser-and-formatter/phone-parser-and-formatter.e2e.spec.ts | 11 | ||||
-rw-r--r-- | src/tools/phone-parser-and-formatter/phone-parser-and-formatter.models.ts | 41 | ||||
-rw-r--r-- | src/tools/phone-parser-and-formatter/phone-parser-and-formatter.vue | 107 | ||||
-rw-r--r-- | src/utils/boolean.test.ts | 9 | ||||
-rw-r--r-- | src/utils/boolean.ts | 6 |
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'; +} |