@@ -90,6 +93,12 @@ export default function Select({
options.flat().find((opt) => opt.default)?.value ||
undefined
}
+ onChange={(e) => {
+ changeHandler?.(
+ e.target.value as (typeof options)[number]["value"]
+ );
+ props.onChange?.(e);
+ }}
>
{options.flat().map((option, index) => {
return (
diff --git a/components/lib/hooks/useWebSocket.tsx b/components/lib/hooks/useWebSocket.tsx
index 64e9597..82878d2 100644
--- a/components/lib/hooks/useWebSocket.tsx
+++ b/components/lib/hooks/useWebSocket.tsx
@@ -25,10 +25,9 @@ export const WebSocketEventNames = ["wsDataEvent", "wsMessageEvent"] as const;
* console.log(e.detail.message) // type string
* })
*/
-export default function useWebSocket
({
- url,
- debounce,
-}: UseWebsocketHookParams) {
+export default function useWebSocket<
+ T extends { [key: string]: any } = { [key: string]: any }
+>({ url, debounce }: UseWebsocketHookParams) {
const DEBOUNCE = debounce || 200;
const [socket, setSocket] = React.useState(
@@ -75,6 +74,8 @@ export default function useWebSocket({
ws.onclose = (ev) => {
console.log("Websocket closed ... Attempting to reconnect ...");
+ console.log("URL:", url);
+
reconnectInterval = setInterval(() => {
if (tries >= 3) {
return window.clearInterval(reconnectInterval);
diff --git a/components/lib/hooks/useWebSocketEventHandler.tsx b/components/lib/hooks/useWebSocketEventHandler.tsx
new file mode 100644
index 0000000..c05952b
--- /dev/null
+++ b/components/lib/hooks/useWebSocketEventHandler.tsx
@@ -0,0 +1,39 @@
+import React from "react";
+import { WebSocketEventNames } from "./useWebSocket";
+
+type Param = {
+ listener?: (typeof WebSocketEventNames)[number];
+};
+
+/**
+ * # Use Websocket Data Event Handler Hook
+ */
+export default function useWebSocketEventHandler<
+ T extends { [key: string]: any } = { [key: string]: any }
+>(param?: Param) {
+ const [data, setData] = React.useState(undefined);
+ const [message, setMessage] = React.useState(undefined);
+
+ React.useEffect(() => {
+ const dataEventListenerCallback = (e: Event) => {
+ const customEvent = e as CustomEvent;
+ const data = customEvent.detail.data as T | undefined;
+ const message = customEvent.detail.message as string | undefined;
+ if (data) setData(data);
+ if (message) setMessage(message);
+ };
+
+ const messageEventName: (typeof WebSocketEventNames)[number] =
+ param?.listener || "wsDataEvent";
+ window.addEventListener(messageEventName, dataEventListenerCallback);
+
+ return function () {
+ window.removeEventListener(
+ messageEventName,
+ dataEventListenerCallback
+ );
+ };
+ }, []);
+
+ return { data, message };
+}
diff --git a/components/lib/layout/Button.tsx b/components/lib/layout/Button.tsx
index cccabcc..3b84e2f 100644
--- a/components/lib/layout/Button.tsx
+++ b/components/lib/layout/Button.tsx
@@ -8,6 +8,36 @@ import {
import { twMerge } from "tailwind-merge";
import Loading from "../elements/Loading";
+export type TWUIButtonProps = DetailedHTMLProps<
+ ButtonHTMLAttributes,
+ HTMLButtonElement
+> & {
+ variant?: "normal" | "ghost" | "outlined";
+ color?:
+ | "primary"
+ | "secondary"
+ | "accent"
+ | "gray"
+ | "error"
+ | "warning"
+ | "success";
+ size?: "small" | "smaller" | "normal" | "large" | "larger";
+ loadingIconSize?: React.ComponentProps["size"];
+ href?: string;
+ target?: HTMLAttributeAnchorTarget;
+ loading?: boolean;
+ linkProps?: DetailedHTMLProps<
+ AnchorHTMLAttributes,
+ HTMLAnchorElement
+ >;
+ beforeIcon?: React.ReactNode;
+ afterIcon?: React.ReactNode;
+ buttonContentProps?: DetailedHTMLProps<
+ HTMLAttributes,
+ HTMLDivElement
+ >;
+};
+
/**
* # Buttons
* @className twui-button-general
@@ -36,35 +66,9 @@ export default function Button({
beforeIcon,
afterIcon,
loading,
+ loadingIconSize,
...props
-}: DetailedHTMLProps<
- ButtonHTMLAttributes,
- HTMLButtonElement
-> & {
- variant?: "normal" | "ghost" | "outlined";
- color?:
- | "primary"
- | "secondary"
- | "accent"
- | "gray"
- | "error"
- | "warning"
- | "success";
- size?: "small" | "smaller" | "normal" | "large" | "larger";
- href?: string;
- target?: HTMLAttributeAnchorTarget;
- loading?: boolean;
- linkProps?: DetailedHTMLProps<
- AnchorHTMLAttributes,
- HTMLAnchorElement
- >;
- beforeIcon?: React.ReactNode;
- afterIcon?: React.ReactNode;
- buttonContentProps?: DetailedHTMLProps<
- HTMLAttributes,
- HTMLDivElement
- >;
-}) {
+}: TWUIButtonProps) {
const finalClassName: string = (() => {
if (variant == "normal" || !variant) {
if (color == "primary" || !color)
@@ -194,6 +198,7 @@ export default function Button({
{
+ if (loadingIconSize) return loadingIconSize;
switch (size) {
case "small":
return "small";
diff --git a/components/lib/utils/fetch/fetchApi.ts b/components/lib/utils/fetch/fetchApi.ts
old mode 100755
new mode 100644
index 358433c..5905769
--- a/components/lib/utils/fetch/fetchApi.ts
+++ b/components/lib/utils/fetch/fetchApi.ts
@@ -1,6 +1,6 @@
import _ from "lodash";
-type FetchApiOptions = {
+type FetchApiOptions = {
method:
| "POST"
| "GET"
@@ -12,7 +12,7 @@ type FetchApiOptions = {
| "delete"
| "put"
| "patch";
- body?: object | string;
+ body?: T | string;
headers?: FetchHeader;
};
@@ -30,13 +30,23 @@ export type FetchApiReturn = {
/**
* # Fetch API
*/
-export default async function fetchApi(
+export default async function fetchApi<
+ T extends { [k: string]: any } = { [k: string]: any },
+ R extends any = any
+>(
url: string,
- options?: FetchApiOptions,
+ options?: FetchApiOptions,
csrf?: boolean,
- /** Key to use to grab local Storage csrf value. */
- localStorageCSRFKey?: string
-): Promise {
+ /**
+ * Key to use to grab local Storage csrf value.
+ */
+ localStorageCSRFKey?: string,
+ /**
+ * Key with which to set the request header csrf
+ * value
+ */
+ csrfHeaderKey?: string
+): Promise {
let data;
const csrfValue = localStorage.getItem(localStorageCSRFKey || "csrf");
@@ -46,7 +56,7 @@ export default async function fetchApi(
} as FetchHeader;
if (csrf && csrfValue) {
- finalHeaders[`'${csrfValue.replace(/\"/g, "")}'`] = "true";
+ finalHeaders[csrfHeaderKey || "x-csrf-key"] = csrfValue;
}
if (typeof options === "string") {
diff --git a/components/pages/Home/(data)/skills.ts b/components/pages/Home/(data)/skills.ts
index 10babcd..6402d4b 100644
--- a/components/pages/Home/(data)/skills.ts
+++ b/components/pages/Home/(data)/skills.ts
@@ -88,6 +88,17 @@ export const skills = {
image: "/images/work/devops/server-management.png",
technologies: ["Node JS", "Bun JS", "Shell Script", "Python"],
},
+ {
+ title: "API Development and Integration",
+ description:
+ "Developing custom APIs and integrating existing APIs from external services",
+ technologies: [
+ "API",
+ "REST",
+ "API Development",
+ "Data Fetching",
+ ],
+ },
{
title: "React JS",
description: "Development of React JS applications for the web",
diff --git a/components/pages/Home/(data)/work.ts b/components/pages/Home/(data)/work.ts
index 5ce2cfa..6667bed 100644
--- a/components/pages/Home/(data)/work.ts
+++ b/components/pages/Home/(data)/work.ts
@@ -45,6 +45,13 @@ export const work = {
"NGINX Reverse Proxy",
],
},
+ {
+ title: "Ifuekosa LLC",
+ description:
+ "Tax Preparation, Notary and Business Consulting Services in New Jersey",
+ href: "https://ifuekosallc.com/",
+ technologies: ["Wordpress", "Docker", "Email Server"],
+ },
],
},
Devops: {