Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 24 additions & 16 deletions src/components/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,26 @@ export interface ScheduleData {
service_at_location_id: string | null;
}

export interface PhoneData {
id: string | null;
number: string;
extension: string | number | null;
type: string | null;
language: string | null;
description: string | null;
createdAt: Date;
updatedAt: Date;
location_id: string | null;
organization_id: string | null;
service_id: string | null;
service_at_location_id: string | null;
}

export type YourPeerLegacyPhoneData = Pick<
PhoneData,
"id" | "number" | "extension" | "type" | "language" | "description"
>;

export interface ServiceData {
id: string;
name: string | null;
Expand Down Expand Up @@ -643,7 +663,7 @@ export interface ServiceData {
updatedAt: Date;
service_id: string;
};
Phones: [];
Phones: PhoneData[];
EventRelatedInfos: {
id: string;
event: string | null;
Expand Down Expand Up @@ -673,22 +693,9 @@ export interface AbstractDetailedLocationData {
url: string | null;
createdAt: Date;
updatedAt: Date;
Phones: string[];
Phones: PhoneData[];
};
Phones: {
id: string | null;
number: string;
extension: string | null;
type: string | null;
language: string | null;
description: string | null;
createdAt: Date;
updatedAt: Date;
location_id: string | null;
organization_id: string | null;
service_id: string | null;
service_at_location_id: string | null;
}[];
Phones: PhoneData[];
Services: ServiceData[];
EventRelatedInfos: {
id: string;
Expand Down Expand Up @@ -858,6 +865,7 @@ export interface YourPeerLegacyLocationData {
last_updated_date: Date;
name: string | null;
phone: string | null;
phones: YourPeerLegacyPhoneData[];
url: string | null;
streetview_url: string | null;
accommodation_services: YourPeerLegacyServiceDataWrapper;
Expand Down
235 changes: 222 additions & 13 deletions src/components/location-detail/location-detail-info.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
"use client";

import { YourPeerLegacyLocationData } from "@/components/common";
import {
YourPeerLegacyLocationData,
YourPeerLegacyPhoneData,
} from "@/components/common";

function normalizeWebsiteUrl(url: string): string | undefined {
if (url) {
Expand All @@ -18,11 +21,204 @@ function renderNormalizedWebsiteUrl(url: string): string | undefined {
}
}

type FormattedPhone = {
display: string;
href?: string;
external?: boolean;
key: string;
};

type PhoneUse = "phone" | "sms" | "whatsapp" | "fax";

const extensionPattern = /(?:ext\.?|extension|x)\s*[:#-]?\s*(\d+)\s*$/i;

function normalizePhoneUse(type?: string | null): PhoneUse {
const normalizedType = (type ?? "").toLowerCase().replace(/[\s_-]+/g, "");

if (normalizedType.includes("whatsapp")) {
return "whatsapp";
}

if (normalizedType.includes("sms") || normalizedType.includes("text")) {
return "sms";
}

if (normalizedType.includes("fax")) {
return "fax";
}

return "phone";
}

function phoneCanHaveExtension(phoneUse: PhoneUse): boolean {
return phoneUse === "phone";
}

function normalizePhoneDigits(rawNumber: string): string {
const digits = rawNumber.replace(/\D/g, "");
return digits.length === 11 && digits.startsWith("1")
? digits.slice(1)
: digits;
}

function formatPhoneDigits(digits: string): string {
if (digits.length === 10) {
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`;
}

if (digits.length === 7) {
return `${digits.slice(0, 3)}-${digits.slice(3)}`;
}

return digits;
}

function getPhoneLabel(
phone: YourPeerLegacyPhoneData,
phoneUse: PhoneUse,
): string | null {
if (phoneUse === "sms") {
return "Text";
}

if (phoneUse === "whatsapp") {
return "WhatsApp";
}

if (phoneUse === "fax") {
return "Fax";
}

const customType = phone.type === "Phone" ? null : phone.type;
return phone.description || customType || null;
}

function getPhoneDetails(
phone: YourPeerLegacyPhoneData,
label: string | null,
): string | null {
if (!phone.description || phone.description === label) {
return null;
}

return phone.description;
}

function formatPhoneContact(
phone: YourPeerLegacyPhoneData,
index: number,
): FormattedPhone | null {
const trimmed = phone.number.trim();
if (!trimmed) {
return null;
}

const phoneUse = normalizePhoneUse(phone.type);
const allowExtension = phoneCanHaveExtension(phoneUse);
const extensionMatch = trimmed.match(extensionPattern);
let extension = `${phone.extension ?? ""}`.trim() || null;
let numberPart = trimmed;

if (extensionMatch) {
if (allowExtension && !extension) {
extension = extensionMatch[1];
}
numberPart = trimmed.slice(0, extensionMatch.index).trim();
}

let digits = normalizePhoneDigits(numberPart);
if (!digits) {
return {
display: trimmed,
key: phone.id ?? `${trimmed}-${index}`,
};
}

if (digits.length > 10) {
if (allowExtension && !extension) {
extension = digits.slice(10);
}
digits = digits.slice(0, 10);
}

if (!allowExtension) {
extension = null;
}

const label = getPhoneLabel(phone, phoneUse);
const details = getPhoneDetails(phone, label);
const formattedNumber = formatPhoneDigits(digits);
const extensionText = extension ? ` x${extension}` : "";
const detailsText = details ? ` (${details})` : "";
const display = `${label ? `${label}: ` : ""}${formattedNumber}${extensionText}${detailsText}`;

if (digits.length < 3 || digits.length > 10) {
return {
display,
key: phone.id ?? `${digits}-${index}`,
};
}

if (phoneUse === "fax") {
return {
display,
key: phone.id ?? `${digits}-${index}`,
};
}

if (phoneUse === "whatsapp") {
return {
display,
href: digits.length === 10 ? `https://wa.me/1${digits}` : undefined,
external: true,
key: phone.id ?? `${digits}-${index}`,
};
}

const dialableNumber = digits.length === 10 ? `+1${digits}` : digits;
const href =
phoneUse === "sms"
? `sms:${dialableNumber}`
: `tel:${dialableNumber}${extension ? `;ext=${extension}` : ""}`;

return {
display,
href,
key: phone.id ?? `${digits}-${index}`,
};
}

function getPhoneContacts(
location: YourPeerLegacyLocationData,
): FormattedPhone[] {
const phones = location.phones.length
? location.phones
: location.phone
? [
{
id: null,
number: location.phone,
extension: null,
type: null,
language: null,
description: null,
},
]
: [];

return phones.flatMap((phone, index) => {
const formattedPhone = formatPhoneContact(phone, index);
return formattedPhone ? [formattedPhone] : [];
});
}

export default function LocationDetailInfo({
location,
}: {
location: YourPeerLegacyLocationData;
}) {
const phoneContacts = getPhoneContacts(location);

return (
<>
{location.closed ? (
Expand Down Expand Up @@ -80,27 +276,40 @@ export default function LocationDetailInfo({
) : undefined}
</p>
</li>
<span>
<>
{!location.closed ? (
<>
{location.phone ? (
<li translate="no" className="flex space-x-3">
{phoneContacts.map((phoneContact) => (
<li
key={phoneContact.key}
translate="no"
className="flex space-x-3"
>
<img
src="/img/icons/phone.svg"
className="flex-shrink-0 w-5 h-5 max-h-5"
alt=""
/>
<p className="text-dark text-sm ml-2">
<a
href={`tel:${location.phone}`}
className="text-blue underline hover:no-underline"
>
{" "}
{location.phone}{" "}
</a>
{phoneContact.href ? (
<a
href={phoneContact.href}
target={phoneContact.external ? "_blank" : undefined}
rel={
phoneContact.external
? "noopener noreferrer"
: undefined
}
className="text-blue underline hover:no-underline"
>
{phoneContact.display}
</a>
) : (
<span>{phoneContact.display}</span>
)}
</p>
</li>
) : undefined}
))}
{location.email ? (
<li translate="no" className="flex space-x-3">
<svg
Expand Down Expand Up @@ -142,7 +351,7 @@ export default function LocationDetailInfo({
) : undefined}
</>
) : undefined}
</span>
</>
</ul>
</>
);
Expand Down
Loading