Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into feat--logicommerce
Browse files Browse the repository at this point in the history
  • Loading branch information
lui-dias committed Jan 9, 2025
2 parents aba81ce + 44e5c4c commit 92a5b0a
Show file tree
Hide file tree
Showing 11 changed files with 272 additions and 40 deletions.
96 changes: 96 additions & 0 deletions blog/loaders/BlogRelatedPosts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* @title BlogRelatedPosts
* @description Retrieves a list of blog related posts.
*
* @param props - The props for the blog related post list.
* @param req - The request object.
* @param ctx - The application context.
* @returns A promise that resolves to an array of blog related posts.
*/
import { RequestURLParam } from "../../website/functions/requestToParam.ts";
import { AppContext } from "../mod.ts";
import { BlogPost, SortBy } from "../types.ts";
import handlePosts, { slicePosts } from "../utils/handlePosts.ts";
import { getRecordsByPath } from "../utils/records.ts";

const COLLECTION_PATH = "collections/blog/posts";
const ACCESSOR = "post";

export interface Props {
/**
* @title Items per page
* @description Number of posts per page to display.
*/
count?: number;
/**
* @title Page query parameter
* @description The current page number. Defaults to 1.
*/
page?: number;
/**
* @title Category Slug
* @description Filter by a specific category slug.
*/
slug?: RequestURLParam | string[];
/**
* @title Page sorting parameter
* @description The sorting option. Default is "date_desc"
*/
sortBy?: SortBy;
/**
* @description Overrides the query term at url
*/
query?: string;
/**
* @title Exclude Post Slug
* @description Excludes a post slug from the list
*/
excludePostSlug?: RequestURLParam | string;
}

/**
* @title BlogRelatedPosts
* @description Retrieves a list of blog related posts.
*
* @param props - The props for the blog related post list.
* @param req - The request object.
* @param ctx - The application context.
* @returns A promise that resolves to an array of blog related posts.
*/

export type BlogRelatedPosts = BlogPost[] | null;

export default async function BlogRelatedPosts(
{ page, count, slug, sortBy, query, excludePostSlug }: Props,
req: Request,
ctx: AppContext,
): Promise<BlogRelatedPosts> {
const url = new URL(req.url);
const postsPerPage = Number(count ?? url.searchParams.get("count") ?? 12);
const pageNumber = Number(page ?? url.searchParams.get("page") ?? 1);
const pageSort = sortBy ?? (url.searchParams.get("sortBy") as SortBy) ??
"date_desc";
const term = query ?? url.searchParams.get("q") ?? undefined;

const posts = await getRecordsByPath<BlogPost>(
ctx,
COLLECTION_PATH,
ACCESSOR,
);

const handledPosts = handlePosts(
posts,
pageSort,
slug,
term,
excludePostSlug,
);

if (!handledPosts) {
return null;
}

const slicedPosts = slicePosts(handledPosts, pageNumber, postsPerPage);

return slicedPosts.length > 0 ? slicedPosts : null;
}
2 changes: 1 addition & 1 deletion blog/loaders/BlogpostList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export default async function BlogPostList(
ctx: AppContext,
): Promise<BlogPost[] | null> {
const url = new URL(req.url);
const postsPerPage = Number(count ?? url.searchParams.get("count"));
const postsPerPage = Number(count ?? url.searchParams.get("count") ?? 12);
const pageNumber = Number(page ?? url.searchParams.get("page") ?? 1);
const pageSort = sortBy ?? url.searchParams.get("sortBy") as SortBy ??
"date_desc";
Expand Down
2 changes: 2 additions & 0 deletions blog/manifest.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import * as $$$5 from "./loaders/BlogpostListing.ts";
import * as $$$2 from "./loaders/BlogPostPage.ts";
import * as $$$6 from "./loaders/Category.ts";
import * as $$$7 from "./loaders/GetCategories.ts";
import * as $$$8 from "./loaders/BlogRelatedPosts.ts";
import * as $$$$$$0 from "./sections/Seo/SeoBlogPost.tsx";
import * as $$$$$$1 from "./sections/Seo/SeoBlogPostListing.tsx";
import * as $$$$$$2 from "./sections/Template.tsx";
Expand All @@ -24,6 +25,7 @@ const manifest = {
"blog/loaders/BlogPostPage.ts": $$$2,
"blog/loaders/Category.ts": $$$6,
"blog/loaders/GetCategories.ts": $$$7,
"blog/loaders/BlogRelatedPosts.ts": $$$8,
},
"sections": {
"blog/sections/Seo/SeoBlogPost.tsx": $$$$$$0,
Expand Down
2 changes: 2 additions & 0 deletions blog/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export interface BlogPost {
* @title Extra Props
*/
extraProps?: ExtraProps[];
/** @hide true */
id?: string;
}

export interface ExtraProps {
Expand Down
32 changes: 27 additions & 5 deletions blog/utils/handlePosts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ export const filterPostsByTerm = (posts: BlogPost[], term: string) =>
)
);

/**
* Returns an filtered BlogPost list
*
* @param posts Posts to be handled
* @param slug Category Slug to be filter
*/
export const filterRelatedPosts = (
posts: BlogPost[],
slug: string[],
) =>
posts.filter(
({ categories }) => categories.find((c) => slug.includes(c.slug)),
);

/**
* Returns an filtered and sorted BlogPost list
*
Expand All @@ -77,11 +91,15 @@ export const slicePosts = (

export const filterPosts = (
posts: BlogPost[],
slug?: string,
slug?: string | string[],
term?: string,
): BlogPost[] => {
if (term) return filterPostsByTerm(posts, term);
if (slug) return filterPostsByCategory(posts, slug);
if (typeof slug === "string") return filterPostsByCategory(posts, slug);
if (Array.isArray(slug)) {
return filterRelatedPosts(posts, slug);
}

return posts;
};

Expand All @@ -90,16 +108,20 @@ export const filterPosts = (
*
* @param posts Posts to be handled
* @param sortBy Sort option (must be: "date_desc" | "date_asc" | "title_asc" | "title_desc" )
* @param slug Category slug to be filter
* @param slug Category slug or an array of slugs to be filtered
* @param term Term to be filter
* @param excludePostSlug Post slug to be excluded
*/
export default function handlePosts(
posts: BlogPost[],
sortBy: SortBy,
slug?: string,
slug?: string | string[],
term?: string,
excludePostSlug?: string,
) {
const filteredPosts = filterPosts(posts, slug, term);
const filteredPosts = filterPosts(posts, slug, term).filter(
({ slug: postSlug }) => postSlug !== excludePostSlug,
);

if (!filteredPosts || filteredPosts.length === 0) {
return null;
Expand Down
8 changes: 7 additions & 1 deletion blog/utils/records.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,11 @@ export async function getRecordsByPath<T>(
const current = Object.entries(resolvables).flatMap(([key, value]) => {
return key.startsWith(path) ? value : [];
});
return (current as Record<string, T>[]).map((item) => item[accessor]);
return (current as Record<string, T>[]).map((item) => {
const id = (item.name as string).split(path)[1]?.replace("/", "");
return {
...item[accessor],
id,
};
});
}
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,5 @@
"jsx": "react-jsx",
"jsxImportSource": "preact"
},
"version": "0.64.11"
"version": "0.64.15"
}
26 changes: 26 additions & 0 deletions vtex/utils/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import type {
LegacyProduct as LegacyProductVTEX,
OrderForm,
PageType as PageTypeVTEX,
PickupHolidays,
PickupPoint,
Product as ProductVTEX,
ProductInventoryData,
Expand Down Expand Up @@ -1120,6 +1121,23 @@ function toHoursSpecification(hours: Hours): OpeningHoursSpecification {
};
}

function toSpecialHoursSpecification(
holiday: PickupHolidays,
): OpeningHoursSpecification {
const dateHoliday = new Date(holiday.date ?? "");
// VTEX provide date in ISO format, at 00h on the day
const validThrough = dateHoliday.setDate(dateHoliday.getDate() + 1)
.toString();

return {
"@type": "OpeningHoursSpecification",
opens: holiday.hourBegin,
closes: holiday.hourEnd,
validFrom: holiday.date,
validThrough,
};
}

function isPickupPointVCS(
pickupPoint: PickupPoint | PickupPointVCS,
): pickupPoint is PickupPointVCS {
Expand All @@ -1135,12 +1153,16 @@ export function toPlace(
latitude,
longitude,
openingHoursSpecification,
specialOpeningHoursSpecification,
} = isPickupPointVCS(pickupPoint)
? {
name: pickupPoint.name,
country: pickupPoint.address?.country?.acronym,
latitude: pickupPoint.address?.location?.latitude,
longitude: pickupPoint.address?.location?.longitude,
specialOpeningHoursSpecification: pickupPoint.pickupHolidays?.map(
toSpecialHoursSpecification,
),
openingHoursSpecification: pickupPoint.businessHours?.map(
toHoursSpecification,
),
Expand All @@ -1150,6 +1172,9 @@ export function toPlace(
country: pickupPoint.address?.country,
latitude: pickupPoint.address?.geoCoordinates[0],
longitude: pickupPoint.address?.geoCoordinates[1],
specialOpeningHoursSpecification: pickupPoint.pickupHolidays?.map(
toSpecialHoursSpecification,
),
openingHoursSpecification: pickupPoint.businessHours?.map((
{ ClosingTime, DayOfWeek, OpeningTime },
) =>
Expand All @@ -1175,6 +1200,7 @@ export function toPlace(
latitude,
longitude,
name,
specialOpeningHoursSpecification,
openingHoursSpecification,
additionalProperty: [{
"@type": "PropertyValue",
Expand Down
7 changes: 7 additions & 0 deletions vtex/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,12 +449,19 @@ export interface PickupStoreInfo {
dockId: null;
}

export interface PickupHolidays {
date?: string;
hourBegin?: string;
hourEnd?: string;
}

export interface PickupPoint {
friendlyName: string;
address: Address;
additionalInfo: string;
id: string;
businessHours: BusinessHour[];
pickupHolidays?: PickupHolidays[];
}

export interface BusinessHour {
Expand Down
92 changes: 92 additions & 0 deletions website/components/OneDollarStats.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Head } from "$fresh/runtime.ts";
import { useScriptAsDataURI } from "@deco/deco/hooks";

export interface Props {
/**
* @description collector address to use
*/
collectorAddress?: string;
}

declare global {
interface Window {
trackCustomEvent: (
name: string,
params: Record<string, string | boolean>,
) => void;
}
}

// we are forcing domain to be deco.cx
// because the domain is separated from the 'u' property
const trackerOriginal =
`"use strict";(()=>{var L;var V=-1,m=function(e){addEventListener("pageshow",function(t){t.persisted&&(V=t.timeStamp,e(t))},!0)},_=function(){var e=self.performance&&performance.getEntriesByType&&performance.getEntriesByType("navigation")[0];if(e&&e.responseStart>0&&e.responseStart<performance.now())return e},I=function(){var e=_();return e&&e.activationStart||0},p=function(e,t){var n=_(),r="navigate";return V>=0?r="back-forward-cache":n&&(document.prerendering||I()>0?r="prerender":document.wasDiscarded?r="restore":n.type&&(r=n.type.replace(/_/g,"-"))),{name:e,value:t===void 0?-1:t,rating:"good",delta:0,entries:[],id:"v4-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12),navigationType:r}},g=function(e,t,n){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){var r=new PerformanceObserver(function(i){Promise.resolve().then(function(){t(i.getEntries())})});return r.observe(Object.assign({type:e,buffered:!0},n||{})),r}}catch{}},v=function(e,t,n,r){var i,o;return function(u){t.value>=0&&(u||r)&&((o=t.value-(i||0))||i===void 0)&&(i=t.value,t.delta=o,t.rating=function(c,a){return c>a[1]?"poor":c>a[0]?"needs-improvement":"good"}(t.value,n),e(t))}},A=function(e){requestAnimationFrame(function(){return requestAnimationFrame(function(){return e()})})},b=function(e){document.addEventListener("visibilitychange",function(){document.visibilityState==="hidden"&&e()})},R=function(e){var t=!1;return function(){t||(e(),t=!0)}},h=-1,B=function(){return document.visibilityState!=="hidden"||document.prerendering?1/0:0},E=function(e){document.visibilityState==="hidden"&&h>-1&&(h=e.type==="visibilitychange"?e.timeStamp:0,ne())},M=function(){addEventListener("visibilitychange",E,!0),addEventListener("prerenderingchange",E,!0)},ne=function(){removeEventListener("visibilitychange",E,!0),removeEventListener("prerenderingchange",E,!0)},D=function(){return h<0&&(h=B(),M(),m(function(){setTimeout(function(){h=B(),M()},0)})),{get firstHiddenTime(){return h}}},k=function(e){document.prerendering?addEventListener("prerenderingchange",function(){return e()},!0):e()},N=[1800,3e3],ie=function(e,t){t=t||{},k(function(){var n,r=D(),i=p("FCP"),o=g("paint",function(u){u.forEach(function(c){c.name==="first-contentful-paint"&&(o.disconnect(),c.startTime<r.firstHiddenTime&&(i.value=Math.max(c.startTime-I(),0),i.entries.push(c),n(!0)))})});o&&(n=v(e,i,N,t.reportAllChanges),m(function(u){i=p("FCP"),n=v(e,i,N,t.reportAllChanges),A(function(){i.value=performance.now()-u.timeStamp,n(!0)})}))})},j=[.1,.25],U=function(e,t){t=t||{},ie(R(function(){var n,r=p("CLS",0),i=0,o=[],u=function(a){a.forEach(function(d){if(!d.hadRecentInput){var ee=o[0],te=o[o.length-1];i&&d.startTime-te.startTime<1e3&&d.startTime-ee.startTime<5e3?(i+=d.value,o.push(d)):(i=d.value,o=[d])}}),i>r.value&&(r.value=i,r.entries=o,n())},c=g("layout-shift",u);c&&(n=v(e,r,j,t.reportAllChanges),b(function(){u(c.takeRecords()),n(!0)}),m(function(){i=0,r=p("CLS",0),n=v(e,r,j,t.reportAllChanges),A(function(){return n()})}),setTimeout(n,0))}))},$=0,S=1/0,y=0,re=function(e){e.forEach(function(t){t.interactionId&&(S=Math.min(S,t.interactionId),y=Math.max(y,t.interactionId),$=y?(y-S)/7+1:0)})},J=function(){return L?$:performance.interactionCount||0},oe=function(){"interactionCount"in performance||L||(L=g("event",re,{type:"event",buffered:!0,durationThreshold:0}))},f=[],T=new Map,Q=0,ae=function(){var e=Math.min(f.length-1,Math.floor((J()-Q)/50));return f[e]},ue=[],ce=function(e){if(ue.forEach(function(i){return i(e)}),e.interactionId||e.entryType==="first-input"){var t=f[f.length-1],n=T.get(e.interactionId);if(n||f.length<10||e.duration>t.latency){if(n)e.duration>n.latency?(n.entries=[e],n.latency=e.duration):e.duration===n.latency&&e.startTime===n.entries[0].startTime&&n.entries.push(e);else{var r={id:e.interactionId,latency:e.duration,entries:[e]};T.set(r.id,r),f.push(r)}f.sort(function(i,o){return o.latency-i.latency}),f.length>10&&f.splice(10).forEach(function(i){return T.delete(i.id)})}}},z=function(e){var t=self.requestIdleCallback||self.setTimeout,n=-1;return e=R(e),document.visibilityState==="hidden"?e():(n=t(e),b(e)),n},x=[200,500],G=function(e,t){"PerformanceEventTiming"in self&&"interactionId"in PerformanceEventTiming.prototype&&(t=t||{},k(function(){var n;oe();var r,i=p("INP"),o=function(c){z(function(){c.forEach(ce);var a=ae();a&&a.latency!==i.value&&(i.value=a.latency,i.entries=a.entries,r())})},u=g("event",o,{durationThreshold:(n=t.durationThreshold)!==null&&n!==void 0?n:40});r=v(e,i,x,t.reportAllChanges),u&&(u.observe({type:"first-input",buffered:!0}),b(function(){o(u.takeRecords()),r(!0)}),m(function(){Q=J(),f.length=0,T.clear(),i=p("INP"),r=v(e,i,x,t.reportAllChanges)}))}))},H=[2500,4e3],P={},K=function(e,t){t=t||{},k(function(){var n,r=D(),i=p("LCP"),o=function(a){t.reportAllChanges||(a=a.slice(-1)),a.forEach(function(d){d.startTime<r.firstHiddenTime&&(i.value=Math.max(d.startTime-I(),0),i.entries=[d],n())})},u=g("largest-contentful-paint",o);if(u){n=v(e,i,H,t.reportAllChanges);var c=R(function(){P[i.id]||(o(u.takeRecords()),u.disconnect(),P[i.id]=!0,n(!0))});["keydown","click"].forEach(function(a){addEventListener(a,function(){return z(c)},!0)}),b(c),m(function(a){i=p("LCP"),n=v(e,i,H,t.reportAllChanges),A(function(){i.value=performance.now()-a.timeStamp,P[i.id]=!0,n(!0)})})}})};function se(e){return e.getAttribute("data-hash-routing")!==null}function W(){return!!(window._phantom||window.__nightmare||window.navigator.webdriver||window.Cypress)}function X(){return window.localStorage.getItem("unexpected_ignore")==="true"}function w(e,t){console.warn('Ignoring event '+ e + t)}function Y(e,t){if(navigator.sendBeacon!==void 0){if(navigator.sendBeacon(e,JSON.stringify(t)))return;console.warn("sendBeacon() didn't queue the request, falling back to fetch()")}fetch(e,{body:JSON.stringify(t),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(n=>console.error('fetch() failed: ' + n.message))}function s(e,t){var n;if(!((n=window.unexpected)===null||n===void 0)&&n.q||(window.unexpected={q:[]}),window.unexpected.q.push(e),t===0){s.timeout&&(clearTimeout(s.timeout.id),s.timeout=null),C();return}let r=()=>{C(),s.timeout=null};if(!s.timeout){s.timeout={id:setTimeout(r,t),delay:t};return}s.timeout.delay>=t&&(clearTimeout(s.timeout.id),s.timeout={id:setTimeout(r,t),delay:t})}(function(e){e.timeout=null})(s||(s={}));function C(){var e;if(!(!((e=window.unexpected)===null||e===void 0)&&e.q)||!window.unexpected.q.length)return;s.timeout!==null&&clearTimeout(s.timeout.id);let t=window.unexpected.q;window.unexpected.q=[];let n={u:t[0].u,v:t[0].v,e:[]};for(let i of t)switch(i.t){case"CwvReport":n.e.push({t:"CwvReport",cls:i.cls,inp:i.inp,lcp:i.lcp});break;case"PageView":n.e.push({t:"PageView",h:i.h,r:i.r});break}let r=Z();Y(r,n)}async function q(){let e=new URL(location.href);return e.search="",
{d:"deco.cx", u:e.href}}
async function O(e){if(W())return w("CwvReport","Running in a headless browser");if(X())return w("CwvReport","Ignore flag is set");s(Object.assign(Object.assign({},await q()),{t:"CwvReport",cls:e.name==="CLS"?e.value:void 0,inp:e.name==="INP"?e.value:void 0,lcp:e.name==="LCP"?e.value:void 0}),5e3)}async function l(){if(W())return w("PageView","Running in a headless browser");if(X())return w("PageView","Ignore flag is set");if(!F&&l.lastPage===location.pathname)return w("PageView","Pathname has not changed");l.lastPage=location.pathname;let e=new URL(location.href),t=document.referrer?new URL(document.referrer):void 0;t&&(t.search=""),s(Object.assign(Object.assign({},await q()),{t:"PageView",h:F,r:t&&t.hostname!==e.hostname?t.href:void 0}),0)}(function(e){e.lastPage=null})(l||(l={}));var de=document.currentScript,F=se(de);U(O);G(O);K(O);if(window.history.pushState){let e=window.history.pushState;window.history.pushState=function(t,n,r){e.apply(this,[t,n,r]),l()},window.addEventListener("popstate",l)}document.visibilityState!=="visible"?document.addEventListener("visibilitychange",()=>{!l.lastPage&&document.visibilityState==="visible"&&l()}):l();document.addEventListener("visibilitychange",()=>{document.visibilityState});document.addEventListener("pagehide",C);async function fe(e,t){let n=Object.assign(Object.assign({},await q()),{e:[{t:e,p:t||void 0,h:F}]}),r=Z();Y(r,n)}function Z(){let e=document.querySelector("#tracker")||document.currentScript,t=e?.getAttribute("data-url");if(!t)throw new Error("No url provided to data-url attribute");return t}window.trackCustomEvent=fe;})();`;

const snippet = () => {
// Flags and additional dimentions
const props: Record<string, string> = {};
const trackPageview = () =>
globalThis.window.trackCustomEvent?.("pageview", props);
// Attach pushState and popState listeners
const originalPushState = history.pushState;
if (originalPushState) {
history.pushState = function () {
// @ts-ignore monkey patch
originalPushState.apply(this, arguments);
trackPageview();
};
addEventListener("popstate", trackPageview);
}
// 2000 bytes limit
const truncate = (str: string) => `${str}`.slice(0, 990);

globalThis.window.DECO.events.subscribe((event) => {
if (!event) {
return;
}
const { name, params } = event;
if (!name || !params || name === "deco") {
return;
}
const values = { ...props };
for (const key in params) {
// @ts-expect-error somehow typescript bugs
const value = params[key];
if (value !== null && value !== undefined) {
values[key] = truncate(
typeof value !== "object" ? value : JSON.stringify(value),
);
}
}
globalThis.window.trackCustomEvent?.(name, values);
});
};

function Component({ collectorAddress }: Props) {
const collector = collectorAddress ?? "https://collector.deco.cx/events";
const tracker = trackerOriginal.replace("COLLECTOR_ADDRESS", collector);

return (
<Head>
<link rel="dns-prefetch" href={collector} />
<link
rel="preconnect"
href={collector}
crossOrigin="anonymous"
/>
<script
dangerouslySetInnerHTML={{
__html: tracker,
}}
id="tracker"
data-url={collector}
/>
<script defer src={useScriptAsDataURI(snippet)} />
</Head>
);
}
export default Component;
Loading

0 comments on commit 92a5b0a

Please sign in to comment.