Skip to content

Commit

Permalink
fix(engagement-ui-feat-like): Added optimistic update for like action
Browse files Browse the repository at this point in the history
  • Loading branch information
sullivanpj committed Dec 22, 2022
1 parent 17e7946 commit d1ced25
Show file tree
Hide file tree
Showing 14 changed files with 137 additions and 31 deletions.
10 changes: 8 additions & 2 deletions apps/web/shell/app/home/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { LikeButton } from "@open-system/engagement-ui-feat-like/like-button";
import { ArticleJsonLd, LogoJsonLd, SocialProfileJsonLd } from "next-seo";
import {
ARTICLE_JSON_LD_DEFAULT,
LOGO_JSON_LD_DEFAULT,
PROFILE_JSON_LD_DEFAULT,
} from "../../next-seo.config";
import LikeButton from "../like-button";
import Client from "./client";

export default function Page() {
const count = 458;

return (
<div className="bg-gradient-to-b from-gray-300/0 via-gray-300/5 to-black">
<ArticleJsonLd {...ARTICLE_JSON_LD_DEFAULT} useAppDir={true} />
Expand All @@ -16,7 +18,11 @@ export default function Page() {

<Client />

<LikeButton pageId="home" className="fixed right-0 bottom-12" />
<LikeButton
pageId="home"
count={count}
className="fixed right-0 bottom-12"
/>
</div>
);
}
17 changes: 17 additions & 0 deletions apps/web/shell/app/like-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"use client";

import { PropsWithBase } from "@open-system/design-system-components";
import { LikeButton as LikeButtonInner } from "@open-system/engagement-ui-feat-like/like-button";

export type LikeButtonProps = PropsWithBase<{
pageId: string;
count: number;
}>;

export default function LikeButton({ className, ...props }: LikeButtonProps) {
return (
<div className={className}>
<LikeButtonInner {...props} />
</div>
);
}
20 changes: 20 additions & 0 deletions libs/core/typescript/utilities/src/boolean-utilities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* It converts a boolean value to a string
* @param {boolean | null} [value] - The value to be converted to a string.
*/
export const stringifyBoolean = (value?: boolean | null): string =>
String(!!value);

/**
* It returns true if the string is "true" or "1", false if the string is "false" or "0", and false if
* the string is anything else.
* @param {string | null} [strValue] - string | null
* @returns A function that takes a string and returns a boolean.
*/
export const parseBoolean = (strValue?: string | null): boolean => {
try {
return !!strValue && Boolean(JSON.parse(strValue));
} catch (e) {
return false;
}
};
12 changes: 6 additions & 6 deletions libs/core/typescript/utilities/src/get-unique-id.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { v4 as uuidv4 } from "uuid";
import { customAlphabet, nanoid } from "nanoid";

/**
* Returns back a unique Id string
*
* @remarks Currently using uuidv4 to generate this string. Please see {@link https://www.npmjs.com/package/uuid | the uuid documentation} for more information.
* @remarks Currently using nanoid to generate this string. Please see {@link https://www.npmjs.com/package/nanoid the Nano ID documentation} for more information.
*
* @returns A unique Id string
*/
export const getUUID = (): string => uuidv4();
export const getUUID = (): string => nanoid();

/**
* Returns back a unique numeric Id string
*
* @remarks Currently using Nano ID to generate this string. Please see {@link https://zelark.github.io/nano-id-cc/ | the Nano ID documentation} for more information.
* @remarks Currently using Nano ID to generate this string. Please see {@link https://zelark.github.io/nano-id-cc/ the Nano ID documentation} for more information.
*
* @param size - The size of the Id. The default size is 21.
* @returns A unique Id string
*/
/*export const getUniqueNumericId = (size?: number | undefined): string =>
customAlphabet("1234567890", size)(size);*/
export const getUniqueNumericId = (size?: number | undefined): string =>
customAlphabet("1234567890", size)(size);
1 change: 1 addition & 0 deletions libs/core/typescript/utilities/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./boolean-utilities";
export * from "./console-logger";
export * from "./custom-utility-class";
export * from "./date-time";
Expand Down
1 change: 1 addition & 0 deletions libs/engagement/ui/feat-like/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./use-is-liked";
36 changes: 36 additions & 0 deletions libs/engagement/ui/feat-like/src/hooks/use-is-liked.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"use client";

import {
parseBoolean,
stringifyBoolean,
} from "@open-system/core-typescript-utilities";
import { destroyCookie, parseCookies, setCookie } from "nookies";
import { useCallback, useEffect, useState } from "react";

export const useIsLiked = (
pageId: string,
initCount = 0
): [boolean, () => void, number] => {
const [isLiked, setIsLiked] = useState(false);
const [count, setCount] = useState(initCount);

useEffect(() => {
setIsLiked(!!parseBoolean(parseCookies()?.[`${pageId}_liked`]));
}, [pageId]);

return [
isLiked,
useCallback(() => {
if (isLiked) {
destroyCookie(null, `${pageId}_liked`);
setIsLiked(false);
setCount(count - 1);
} else {
setCookie(null, `${pageId}_liked`, stringifyBoolean(true));
setIsLiked(true);
setCount(count + 1);
}
}, [count, isLiked, pageId]),
count,
];
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { LikeButton } from "./LikeButton";

describe("LikeButton", () => {
it("should render successfully", () => {
const { baseElement } = render(<LikeButton pageId="pageId" />);
const { baseElement } = render(<LikeButton pageId="pageId" count={100} />);
expect(baseElement).toBeTruthy();
});
});
22 changes: 14 additions & 8 deletions libs/engagement/ui/feat-like/src/like-button/LikeButton.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
"use client";

import { PropsWithBase } from "@open-system/design-system-components";
import clsx from "clsx";
import CheckIcon from "../../assets/heart-check.svg";
import PlusIcon from "../../assets/heart-plus.svg";
import { useIsLiked } from "../hooks/use-is-liked";

export type LikeButtonProps = PropsWithBase<{
pageId: string;
count: number;
}>;

export function LikeButton({ className, ...props }: LikeButtonProps) {
const count = 458;
const isLiked = false;
export function LikeButton({ pageId, ...props }: LikeButtonProps) {
const [isLiked, toggleLike, count] = useIsLiked(pageId, props?.count);

return (
<div className={clsx(className, "group z-like h-fit w-fit cursor-pointer")}>
<div
onClick={toggleLike}
className="group z-like h-fit w-fit cursor-pointer">
<div className="relative mb-7 group-hover:animate-bounce">
{isLiked ? (
<CheckIcon className="w-32" />
) : (
<PlusIcon className="w-32" />
)}
<label className="absolute top-10 left-[2.7rem] cursor-pointer font-like-label text-2xl text-primary transition duration-300 group-hover:text-quaternary">
{count}
</label>
<div className="absolute top-10 flex w-full justify-center text-center">
<label className="inset-0 mx-auto cursor-pointer font-like-label text-3xl text-primary transition duration-300 group-hover:text-quaternary">
{count}
</label>
</div>
</div>
<div className="absolute bottom-0 flex w-full justify-center px-4 pb-2">
<label className="cursor-pointer font-like-label text-2xl text-primary transition duration-300 group-hover:text-quaternary group-hover:underline">
Expand Down
2 changes: 2 additions & 0 deletions libs/engagement/ui/feat-like/src/like-button/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
"use client";

export * from "./LikeButton";
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export type NotificationBannerProps = PropsWithBase<{
}>;

/**
* The base MessageBar component used by the Open System repository
* The base NotificationBanner component used by the Open System repository
*/
export const NotificationBanner = forwardRef<
ModalReference,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export type NotificationGroupProps = PropsWithBase<{
}>;

/**
* A component to handle NextJS/application specific logic for the Model design component.
* The base NotificationGroup component used by the Open System repository
*/
export const NotificationGroup = ({
children,
Expand Down
37 changes: 27 additions & 10 deletions package-lock.json

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

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,7 @@
"react-router-dom": "^6.4.1",
"regenerator-runtime": "0.13.7",
"rimraf": "^3.0.2",
"tslib": "^2.3.0",
"uuid": "^9.0.0"
"tslib": "^2.3.0"
},
"devDependencies": {
"@asyncapi/generator": "^1.9.13",
Expand Down Expand Up @@ -180,6 +179,7 @@
"log4brains": "^1.0.1",
"mustache": "^4.2.0",
"next-seo": "^5.14.1",
"nookies": "^2.5.2",
"nx": "15.3.0-rc.0",
"postcss": "^8.4.19",
"prettier": "^2.6.2",
Expand Down

0 comments on commit d1ced25

Please sign in to comment.