8000 V2 `whatsapp` by michaelgatuma · Pull Request #437 · finsweet/attributes · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

V2 whatsapp #437

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
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
3 changes: 2 additions & 1 deletion packages/attributes/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@
"@finsweet/attributes-starrating": "workspace:*",
"@finsweet/attributes-toc": "workspace:*",
"@finsweet/attributes-utils": "workspace:*",
"@finsweet/attributes-videohls": "workspace:*"
"@finsweet/attributes-videohls": "workspace:*",
"@finsweet/attributes-whatsapp": "workspace:*"
},
"devDependencies": {
"@types/node": "^18.16.1",
Expand Down
4 changes: 4 additions & 0 deletions packages/attributes/src/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ export const loadAttribute = async (solution: FsAttributeKey) => {
return import('@finsweet/attributes-videohls');
}

case 'whatsapp': {
return import('@finsweet/attributes-whatsapp');
}

default: {
throw `Finsweet Attribute "${solution}" is not supported.`;
}
Expand Down
75 changes: 75 additions & 0 deletions packages/attributes/tests/whatsapp.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { type ElementHandle, expect, test } from '@playwright/test';

import { waitAttributeLoaded } from './utils';

let whatsappButtons: ElementHandle<HTMLAnchorElement>[] = [];

test.beforeEach(async ({ page }) => {
await page.goto('https://dev-attributes-whatsapp.webflow.io');
await waitAttributeLoaded(page, 'whatsapp');

// Get all whatsapp buttons
whatsappButtons = (await page.$$('a[fs-whatsapp-element="button"]')) as ElementHandle<HTMLAnchorElement>[];
});

test.describe('whatsapp', () => {
// Static attributes test
test('Validates static attributes for whatsapp anchor tags', async () => {
// Loop through all whatsapp buttons and validate the attributes
for (const button of whatsappButtons) {
const { phoneNumber, message } = await getAttributes(button);

const expectedPhoneNumber = await button.getAttribute('fs-whatsapp-phone');
const expectedMessage = await button.getAttribute('fs-whatsapp-message');

// Assert that the attributes are not empty
if (expectedPhoneNumber && expectedMessage) {
expect(phoneNumber).toBe(expectedPhoneNumber);
expect(message).toBe(expectedMessage);
}
}
});

// Dynamic attributes test
test('Validates dynamic attributes for whatsapp anchor tags', async () => {
// Loop through all whatsapp buttons and validate the attributes
for (const button of whatsappButtons) {
// Get the attributes from the whatsapp button
const { phoneNumber, message } = await getAttributes(button);

const phoneElement: ElementHandle<Element> | null = await button.$('div[fs-whatsapp-element="phone"]');
const messageElement: ElementHandle<Element> | null = await button.$('div[fs-whatsapp-element="message"]');

// Assert that the attributes are not empty
if (phoneElement && messageElement) {
const dynamicMessage = (await messageElement.textContent()) ?? '';
expect(dynamicMessage).toBeTruthy();

const dynamicPhone = (await phoneElement.textContent()) ?? '';
expect(dynamicPhone).toBeTruthy();

expect(dynamicPhone).toBe(phoneNumber);
expect(dynamicMessage).toBe(message);
}
}
});
});

/**
* Helper function that gets the attributes from a whatsapp button
* @param button The whatsapp button
* @returns An object containing the phone number and message
*/
async function getAttributes(button: ElementHandle<HTMLAnchorElement>) {
const href = (await button.getAttribute('href')) ?? '';
expect(href).toBeTruthy();

// assert that the href value is a valid whatsapp link
expect(href).toContain('https://wa.me/');

const url = new URL(href);
const [, phoneNumber] = url.pathname.split('/');
const message = url.searchParams.get('text');

return { phoneNumber, message };
}
2 changes: 2 additions & 0 deletions packages/utils/src/constants/attributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,5 @@ export const TOC_ATTRIBUTE = 'toc';
export const READ_TIME_ATTRIBUTE = 'readtime';

export const VIDEO_HLS_ATTRIBUTE = 'videohls';

export const WHATSAPP_ATTRIBUTE = 'whatsapp';
3 changes: 3 additions & 0 deletions packages/whatsapp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `whatsapp` Attribute

Adds a simple url to an anchor element that creates a new whatsapp chat in a new tab when clicked.
17 changes: 17 additions & 0 deletions packages/whatsapp/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@finsweet/attributes-whatsapp",
"version": "1.0.0",
"description": "Adds a simple url to an anchor element that creates a new whatsapp chat in a new tab when clicked.",
"private": true,
"type": "module",
"types": "src/index.ts",
"exports": {
".": {
"types": "./src/index.ts",
"import": "./src/index.ts"
}
},
"dependencies": {
"@finsweet/attributes-utils": "workspace:*"
}
}
30 changes: 30 additions & 0 deletions packages/whatsapp/src/factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { formatPhoneNumber, formatUrl, getAttribute, getInstanceIndex, queryElement, WHATSAPP_BASE_URL } from './utils';

/**
* Initialize a WhatsApp button element instance
* @param buttonElement The button element to initialize
* @returns The initialized button element
*/
export const initWhatsappInstance = (buttonElement: Element) => {
// static
let phone = getAttribute(buttonElement, 'phone');
let message = getAttribute(buttonElement, 'message');

// dynamic
if (!message && !phone) {
const instanceIndex = getInstanceIndex(buttonElement);
// phone
phone = queryElement('phone', { instanceIndex, scope: buttonElement })?.textContent ?? '';
// message
message = queryElement('message', { instanceIndex, scope: buttonElement })?.textContent ?? '';
}

if (!phone || !message) return; //todo: throw new Error('Missing phone or message attribute');

// format phone number and url
phone = formatPhoneNumber(phone);
const url = formatUrl(`${WHATSAPP_BASE_URL}/${phone}`, { text: message.trim() });

buttonElement.setAttribute('href', url);
buttonElement.setAttribute('target', '_blank');
};
3 changes: 3 additions & 0 deletions packages/whatsapp/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { version } from '../package.json';
export { init } from './init';
export { ELEMENTS, SETTINGS } from './utils';
27 changes: 27 additions & 0 deletions packages/whatsapp/src/init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { type FsAttributeInit, isNotEmpty, waitAttributeLoaded, waitWebflowReady } from '@finsweet/attributes-utils';

import { initWhatsappInstance } from './factory';
import { queryAllElements } from './utils';

/**
* Inits the attribute.
*/
export const init: FsAttributeInit = async () => {
await waitWebflowReady();

// wait for cms to load if it's present
await waitAttributeLoaded('cmsload');

// Get all whatsapp buttons on the page
const buttonElements = queryAllElements('button');

// Create instances of each whatsapp button
buttonElements.map(initWhatsappInstance).filter(isNotEmpty);

return {
result: buttonElements,
destroy() {
return;
},
};
};
36 changes: 36 additions & 0 deletions packages/whatsapp/src/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { type AttributeElements, type AttributeSettings } from '@finsweet/attributes-utils';

export const ELEMENTS = [
/**
* Defines a button element.
*/
'button',
/**
* Defines a phone element
*/
'phone',
/**
* Defines a message element.
*/
'message',
] as const satisfies AttributeElements;

export const SETTINGS = {
/**
* Defines the WhatsApp phone number.
*/
phone: {
key: 'phone',
},
/**
* Defines the WhatsApp message.
*/
message: {
key: 'message',
},
} as const satisfies AttributeSettings;

/**
* Defines the WhatsApp base URL.
*/
export const WHATSAPP_BASE_URL = 'https://wa.me';
23 changes: 23 additions & 0 deletions packages/whatsapp/src/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Formats a url with the given params as query params
* @param url The url to format
* @param params The params to append to the url
* @returns The formatted url
*/
export const formatUrl = (url: string, params: { [key: string]: string }): string => {
const urlObject = new URL(url);
for (const [key, value] of Object.entries(params)) {
urlObject.searchParams.append(key, value);
}

return urlObject.href;
};

/**
* Formats a phone number by removing all non-numeric characters and appending +
* @param phoneNumber The phone number to format
* @returns The formatted phone number
*/
export const formatPhoneNumber = (phoneNumber: string): string => {
return '+' + phoneNumber.replace(/[^0-9]/g, '');
};
4 changes: 4 additions & 0 deletions packages/whatsapp/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './constants';
export * from './helpers';
export * from './schema';
export * from './selectors';
43 changes: 43 additions & 0 deletions packages/whatsapp/src/utils/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { Schema, SchemaSettings } from '@finsweet/attributes-utils';

import { ELEMENTS, SETTINGS } from '.';

const SCHEMA_SETTINGS: SchemaSettings<typeof SETTINGS> = {
phone: {
...SETTINGS.phone,
name: 'Phone',
description: 'The phone number',
type: 'text',
},
message: {
...SETTINGS.message,
name: 'Message',
description: 'The message to send',
type: 'text',
},
};

export const schema: Schema<typeof ELEMENTS, typeof SETTINGS> = {
groups: [],
elements: [
{
key: 'button',
name: 'Button',
description: 'A button to open WhatsApp',
allowedTypes: ['Link'],
settings: [SCHEMA_SETTINGS.phone, SCHEMA_SETTINGS.message],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we have another element without the settings? for dynamic elements this button is just a wrapper and has no fs-whatsapp-phone or fs-whatsapp-message
🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe @alexiglesias93 can advise on this.

},
{
key: 'phone',
name: 'Phone',
description: 'A phone number to open WhatsApp',
allowedTypes: ['Block'],
},
{
key: 'message',
name: 'Message',
description: 'A message to open WhatsApp',
allowedTypes: ['Block'],
},
],
};
9 changes: 9 additions & 0 deletions packages/whatsapp/src/utils/selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { generateSelectors, WHATSAPP_ATTRIBUTE } from '@finsweet/attributes-utils';

import { ELEMENTS, SETTINGS } from '.';

export const { getAttribute, hasAttributeValue, queryAllElements, getInstanceIndex, queryElement } = generateSelectors(
WHATSAPP_ATTRIBUTE,
ELEMENTS,
SETTINGS
);
3 changes: 3 additions & 0 deletions packages/whatsapp/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}
34 changes: 10 additions & 24 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0