Adding in changes

This commit is contained in:
Frank
2023-10-02 00:05:06 -06:00
parent 39d72a5cf3
commit d61a112753
30 changed files with 2825 additions and 156 deletions

16
components.json Normal file
View File

@@ -0,0 +1,16 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "@/tailwind.config.ts",
"css": "@/src/styles/globals.css",
"baseColor": "slate",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/utils/utils"
}
}

1666
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
{
"name": "next-wordpress-test",
"name": "frankdelaguila-portfolio",
"version": "0.1.0",
"private": true,
"scripts": {
@@ -9,17 +9,40 @@
"lint": "next lint"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.4.2",
"@fortawesome/free-brands-svg-icons": "^6.4.2",
"@fortawesome/free-regular-svg-icons": "^6.4.2",
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-navigation-menu": "^1.1.3",
"@radix-ui/react-slot": "^1.0.2",
"@react-three/drei": "^9.85.0",
"@react-three/fiber": "^8.14.3",
"@reduxjs/toolkit": "^1.9.5",
"@types/node": "20.6.3",
"@types/react": "18.2.22",
"@types/react-dom": "18.2.7",
"@types/redux-logger": "^3.0.9",
"autoprefixer": "10.4.16",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"eslint": "8.49.0",
"eslint-config-next": "13.5.2",
"html-react-parser": "^4.2.2",
"lucide-react": "^0.279.0",
"next": "13.5.2",
"next-redux-wrapper": "^8.1.0",
"postcss": "8.4.30",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-redux": "^8.1.2",
"react-responsive": "^9.0.2",
"redux-logger": "^3.0.6",
"tailwind-merge": "^1.14.0",
"tailwindcss": "3.3.3",
"tailwindcss-animate": "^1.0.7",
"three": "^0.156.1",
"typescript": "5.2.2"
}
}

View File

@@ -0,0 +1,21 @@
import { Skeleton } from "@/components/ui/skeleton";
import { useGetPostImageQuery } from "@/services/posts";
interface BlogImageTypes {
imageID: number;
}
const BlogImage = ({ imageID }: BlogImageTypes): JSX.Element => {
const { data: image, isLoading } = useGetPostImageQuery(imageID);
// console.log(image.source_url);
return isLoading ? (
<Skeleton className="bg-accent h-64 w-full rounded-4" />
) : (
<div
style={{ backgroundImage: `url(${image.source_url})` }}
className="block bg-cover bg-no-repeat bg-center transition-all hover:scale-105 w-full h-64 rounded-lg"
></div>
);
};
export default BlogImage;

View File

@@ -0,0 +1,17 @@
import clsx from "clsx";
import Navigation from "./Navigation";
interface InternalTypes {
children?: React.ReactNode;
}
const Internal = ({ children }: InternalTypes): JSX.Element => {
return (
<main>
<Navigation />
{children ? children : null}
</main>
);
};
export default Internal;

View File

@@ -0,0 +1,43 @@
import { faSpinner } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
const Loading = (): JSX.Element => {
const router = useRouter();
const [showLoading, setShowLoader] = useState(false);
const handleRouteChange = () => {
setShowLoader(true);
};
const handleRouteComplete = () => {
setShowLoader(false);
};
useEffect(() => {
router.events.on("routeChangeStart", handleRouteChange);
router.events.on("routeChangeComplete", handleRouteComplete);
return () => {
router.events.off("routeChangeStart", handleRouteChange);
};
}, []);
return (
<div
className={`${
showLoading ? "opacity-100" : "opacity-0"
} fixed flex items-center transition-all pointer-events-none justify-center bg-background top-0 pointer-events-none left-0 text-foreground w-screen h-screen z-[51]`}
>
<h1 className="flex flex-col text-2xl font-bold opacity-100">
<FontAwesomeIcon
icon={faSpinner}
spin
size="2xl"
className="mb-4"
/>
Loading the next page...
</h1>
</div>
);
};
export default Loading;

View File

@@ -0,0 +1,184 @@
import React, { useEffect, useState } from "react";
import Link from "next/link";
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faBars, faLaptopCode } from "@fortawesome/free-solid-svg-icons";
import { useMediaQuery } from "react-responsive";
import { Skeleton } from "../ui/skeleton";
import { Sheet, SheetContent, SheetHeader, SheetTrigger } from "../ui/sheet";
import { useGetPagesQuery } from "@/services/pages";
import { cn } from "@/utils/utils";
import { Button } from "../ui/button";
const styles = {
fontFamily: "'Ubuntu', sans-serif",
};
const ListItem = React.forwardRef<
React.ElementRef<"a">,
React.ComponentPropsWithoutRef<"a">
>(({ className, title, children, ...props }, ref) => {
return (
<li>
<NavigationMenuLink asChild>
<a
ref={ref}
className={cn(
"block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
className
)}
{...props}
>
<div className="text-sm font-medium leading-none">{title}</div>
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
{children}
</p>
</a>
</NavigationMenuLink>
</li>
);
});
ListItem.displayName = "ListItem";
const Navigation = (props: any): JSX.Element => {
const [colorChange, setColorChange] = useState<boolean>(false);
const changeNavbarColor = () => {
if (window.scrollY > 0) {
setColorChange(true);
} else {
setColorChange(false);
}
};
const { data: pages, isSuccess, isLoading } = useGetPagesQuery();
const isTabletOrMobile = useMediaQuery({ query: "(max-width: 1224px)" });
useEffect(() => {
window.addEventListener("scroll", changeNavbarColor);
}, []);
return (
<nav
className={`${
colorChange ? "bg-background" : ""
} transition-colors text-foreground fixed flex gap-8 justify-between items-center w-full top-0 inset-x-0 p-4 md:p-8 z-20`}
>
{isSuccess && !isLoading ? (
<>
<Link
href="/"
className="p-2"
style={{ fontFamily: "'Ubuntu', sans-serif" }}
>
<div className="flex gap-2 md:gap-4 items-center">
<FontAwesomeIcon
icon={faLaptopCode}
size="2x"
/>
<p className="font-bold font-sans text-2xl md:text-4xl">
Frank Delaguila
</p>
</div>
</Link>
{isTabletOrMobile ? (
<Sheet>
<SheetTrigger asChild>
<Button className="bg-gray-700">
<FontAwesomeIcon
icon={faBars}
size="lg"
className="text-white"
/>
</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<Link
href="/"
className="p-2"
>
<div className="flex gap-2 md:gap-4 items-center">
<FontAwesomeIcon
icon={faLaptopCode}
size="2x"
/>
<p className="font-bold font-sans text-2xl md:text-3xl">
Frank Delaguila
</p>
</div>
</Link>
</SheetHeader>
<div className="grid gap-4 py-4">
<Link
href="/"
className="p-2"
>
Home
</Link>
{pages?.map((page: any) => (
<Link
key={page.id}
href={`/${page.slug}`}
className="p-2"
>
{page.title.rendered}
</Link>
))}
<Link
href="/blog"
className="p-2"
>
Blog
</Link>
</div>
</SheetContent>
</Sheet>
) : (
<div className="flex gap-4">
<Link href="/">
<Button
size={"lg"}
variant={"ghost"}
>
Home
</Button>
</Link>
{pages?.map((page: any) => (
<Link
key={page.id}
href={`/${page.slug}`}
className="w-full"
>
<Button
size={"lg"}
variant={"ghost"}
>
{page.title.rendered}
</Button>
</Link>
))}
<Link href="/blog">
<Button
variant={"ghost"}
size={"lg"}
>
Blog
</Button>
</Link>
</div>
)}
</>
) : (
<Skeleton className="bg-accent h-12 w-full" />
)}
</nav>
);
};
export default Navigation;

View File

@@ -0,0 +1,17 @@
import { faChevronDown } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
const ScrollDown = (): JSX.Element => {
return (
<div className="absolute text-foreground bottom-0 flex flex-col items-center inset-x-0 mb-4 z-10">
<p className="mb-2">Scroll Down!</p>
<FontAwesomeIcon
icon={faChevronDown}
bounce
size="lg"
/>
</div>
);
};
export default ScrollDown;

View File

@@ -0,0 +1,56 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/utils/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
}
);
Button.displayName = "Button";
export { Button, buttonVariants };

View File

@@ -0,0 +1,128 @@
import * as React from "react";
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
import { cva } from "class-variance-authority";
import { ChevronDown } from "lucide-react";
import { cn } from "@/utils/utils";
const NavigationMenu = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Root
ref={ref}
className={cn(
"relative z-10 flex max-w-max flex-1 items-center justify-center",
className
)}
{...props}
>
{children}
<NavigationMenuViewport />
</NavigationMenuPrimitive.Root>
));
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;
const NavigationMenuList = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.List>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.List
ref={ref}
className={cn(
"group flex flex-1 list-none items-center justify-center space-x-1",
className
)}
{...props}
/>
));
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
const NavigationMenuItem = NavigationMenuPrimitive.Item;
const navigationMenuTriggerStyle = cva(
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
);
const NavigationMenuTrigger = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Trigger
ref={ref}
className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props}
>
{children}{" "}
<ChevronDown
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
));
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;
const NavigationMenuContent = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Content
ref={ref}
className={cn(
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
className
)}
{...props}
/>
));
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;
const NavigationMenuLink = NavigationMenuPrimitive.Link;
const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
<div className={cn("absolute left-0 top-full flex justify-center")}>
<NavigationMenuPrimitive.Viewport
className={cn(
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
className
)}
ref={ref}
{...props}
/>
</div>
));
NavigationMenuViewport.displayName =
NavigationMenuPrimitive.Viewport.displayName;
const NavigationMenuIndicator = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Indicator
ref={ref}
className={cn(
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
className
)}
{...props}
>
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Indicator>
));
NavigationMenuIndicator.displayName =
NavigationMenuPrimitive.Indicator.displayName;
export {
navigationMenuTriggerStyle,
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
};

147
src/components/ui/sheet.tsx Normal file
View File

@@ -0,0 +1,147 @@
import * as React from "react";
import * as SheetPrimitive from "@radix-ui/react-dialog";
import { cva, type VariantProps } from "class-variance-authority";
import { X } from "lucide-react";
import { cn } from "@/utils/utils";
const Sheet = SheetPrimitive.Root;
const SheetTrigger = SheetPrimitive.Trigger;
const SheetClose = SheetPrimitive.Close;
const SheetPortal = ({
// @ts-ignore
className,
...props
}: SheetPrimitive.DialogPortalProps) => (
<SheetPrimitive.Portal
// @ts-ignore
className={cn(className)}
{...props}
/>
);
SheetPortal.displayName = SheetPrimitive.Portal.displayName;
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
ref={ref}
/>
));
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
{
variants: {
side: {
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom:
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
},
},
defaultVariants: {
side: "right",
},
}
);
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = "right", className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ side }), className)}
{...props}
>
{children}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
));
SheetContent.displayName = SheetPrimitive.Content.displayName;
const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
);
SheetHeader.displayName = "SheetHeader";
const SheetFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
);
SheetFooter.displayName = "SheetFooter";
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold text-foreground", className)}
{...props}
/>
));
SheetTitle.displayName = SheetPrimitive.Title.displayName;
const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
));
SheetDescription.displayName = SheetPrimitive.Description.displayName;
export {
Sheet,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
};

View File

@@ -0,0 +1,15 @@
import { cn } from "@/utils/utils";
function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-muted", className)}
{...props}
/>
);
}
export { Skeleton };

2
src/lib/redux/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export * from './store';
export * from './slices';

View File

@@ -0,0 +1,10 @@
/* Core */
import { pagesApi } from '@/services/pages'
import { postsApi } from '@/services/posts'
const middleware = [
postsApi.middleware,
pagesApi.middleware,
]
export { middleware }

View File

@@ -0,0 +1,7 @@
import { pagesApi } from "@/services/pages";
import { postsApi } from "@/services/posts";
export const reducer = {
[postsApi.reducerPath]: postsApi.reducer,
[pagesApi.reducerPath]: pagesApi.reducer,
};

View File

@@ -0,0 +1 @@
export * from './selectors';

View File

@@ -0,0 +1,4 @@
/* Instruments */
import type { ReduxState } from '@/lib/redux';
export const selectCount = (state: ReduxState) => state.counter.value;

30
src/lib/redux/store.ts Normal file
View File

@@ -0,0 +1,30 @@
/* Core */
import { configureStore, type ThunkAction, type Action } from "@reduxjs/toolkit";
import {
useSelector as useReduxSelector,
useDispatch as useReduxDispatch,
type TypedUseSelectorHook,
} from "react-redux";
import { createWrapper } from "next-redux-wrapper";
/* Instruments */
import { reducer } from './rootReducer';
import { middleware } from './middleware';
export const reduxStore = () => configureStore({
reducer,
middleware: (getDefaultMiddleware) => {
return getDefaultMiddleware().concat(middleware);
}
});
/* Types */
export type ReduxStore = ReturnType<typeof reduxStore>;
export type ReduxState = ReturnType<ReduxStore['getState']>;
export type ReduxDispatch = ReduxStore['dispatch'];
export type ReduxThunkAction<ReturnType = void> = ThunkAction<ReturnType, ReduxState, unknown, Action>;
export const useDispatch = () => useReduxDispatch<ReduxDispatch>();
export const useSelector: TypedUseSelectorHook<ReduxState> = useReduxSelector;
export const wrapper = createWrapper<ReduxStore>(reduxStore);

44
src/pages/[slug].tsx Normal file
View File

@@ -0,0 +1,44 @@
import parser from "html-react-parser";
import { getPage, getPages, getRunningQueriesThunk } from "@/services/pages";
import { reduxStore, wrapper } from "@/lib/redux";
import Internal from "@/components/common/Internal";
export const getStaticPaths = async () => {
const store = reduxStore();
const { data: pages } = await store.dispatch(getPages.initiate());
return {
paths: pages.map((page: any) => ({
params: {
slug: page.slug,
},
})),
fallback: false,
};
};
export const getStaticProps = wrapper.getStaticProps(
(store) => async (context) => {
store.dispatch(getPage.initiate(context?.params?.slug));
const [page] = await Promise.all(store.dispatch(getRunningQueriesThunk()));
return {
props: {
page: page.data[0],
},
};
}
);
const Page = ({ page }: any) => {
return (
<Internal>
<div className="container my-32">
{parser(page.content.rendered, { trim: true })}
</div>
</Internal>
);
};
export default Page;

View File

@@ -1,6 +1,19 @@
import '@/styles/globals.css'
import type { AppProps } from 'next/app'
import { wrapper } from "@/lib/redux";
import { Provider } from "react-redux";
import type { AppProps } from "next/app";
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
import "@/styles/globals.css";
import { config } from "@fortawesome/fontawesome-svg-core";
import "@fortawesome/fontawesome-svg-core/styles.css";
import Loading from "@/components/common/Loading";
config.autoAddCss = false;
export default function App({ Component, ...rest }: AppProps) {
const { store, props } = wrapper.useWrappedStore(rest);
return (
<Provider store={store}>
<Component {...props.pageProps} />
<Loading />
</Provider>
);
}

View File

@@ -1,13 +1,29 @@
import { Html, Head, Main, NextScript } from 'next/document'
import { Html, Head, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html lang="en">
<Head />
<Html
lang="en"
className="dark"
>
<Head>
<link
rel="preconnect"
href="https://fonts.googleapis.com"
/>
<link
rel="preconnect"
href="https://fonts.gstatic.com"
/>
<link
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&family=Ubuntu:wght@400;700&display=swap"
rel="stylesheet"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
);
}

View File

@@ -0,0 +1,83 @@
import parser from "html-react-parser";
import { getPost, getPosts, getRunningQueriesThunk } from "@/services/posts";
import { wrapper, reduxStore } from "@/lib/redux";
import Internal from "@/components/common/Internal";
import ScrollDown from "@/components/common/scrollDown";
export const getStaticPaths = async () => {
const store = reduxStore();
const { data: posts } = await store.dispatch(getPosts.initiate());
return {
paths: posts.map((post: any) => ({
params: {
slug: post.slug,
},
})),
fallback: false,
};
};
export const getStaticProps = wrapper.getStaticProps(
(store) => async (context) => {
store.dispatch(getPost.initiate(context?.params?.slug));
const [post] = await Promise.all(store.dispatch(getRunningQueriesThunk()));
return {
props: {
post: post.data[0],
},
};
}
);
const Post = ({ post }: any) => {
const dateUploaded = new Date(post.date).toDateString();
const modified = new Date(post.modified).toDateString();
return (
<Internal>
<div className="bg-gray-800 flex justify-center items-center h-72 md:h-[32rem] overflow-hidden text-white relative">
<video
id="background-video"
autoPlay
loop
muted
poster="https://www.frankdelaguila.com/wp-content/uploads/2023/09/Screen-Shot-2023-09-26-at-11.49.22-PM.png"
className="w-screen h-screen object-cover top-0 left-0 bottom-0 right-0"
>
<source
src="https://www.frankdelaguila.com/wp-content/uploads/2023/09/video-2160p.mp4"
type="video/mp4"
></source>
</video>
<h1 className="absolute text-center text-4xl font-bold text-foreground inset-y-auto inset-x-0">
{post.title.rendered}
</h1>
<ScrollDown />
</div>
<div className="flex flex-col md:flex-row items-center my-10 text-center md:w-3/4 mx-auto">
<img
src="https://www.frankdelaguila.com/wp-content/uploads/2023/09/frankdelaguila-headshot.jpeg"
alt="Photo of Frank Delaguila - Glasses, Moustache, Hat, smiling"
className="block w-1/3 md:w-1/12 rounded-full border-2 border-solid border-primary"
></img>
<div className="flex flex-col md:flex-row justify-between items-center w-full">
<div className="ml-4 mb-4 md:mb-0 md:text-left">
<h4 className="font-bold font-sans text-2xl">Frank Delaguila</h4>
<p className="font-light">Software Engineer / UX UI Designer</p>
</div>
<div className="flex-col md:text-right">
<h4>Post Uploaded on: {dateUploaded}</h4>
<p>Modified: {modified}</p>
</div>
</div>
</div>
<div className="post-content mb-24 px-8 md:px-0 md:w-3/4 mx-auto">
{parser(post.content.rendered, { trim: true })}
</div>
</Internal>
);
};
export default Post;

48
src/pages/blog/index.tsx Normal file
View File

@@ -0,0 +1,48 @@
import Link from "next/link";
import { getPosts, getRunningQueriesThunk } from "@/services/posts";
import { wrapper } from "@/lib/redux";
import Internal from "@/components/common/Internal";
import BlogImage from "@/components/common/BlogImage/BlogImage";
export const getStaticProps = wrapper.getStaticProps(
(store) => async (context) => {
store.dispatch(getPosts.initiate());
const [posts] = await Promise.all(store.dispatch(getRunningQueriesThunk()));
return {
props: {
posts: posts.data,
},
};
}
);
export default function Posts({ posts }: any): JSX.Element {
return (
<Internal>
<div className="container mt-32 mb-12">
<div className="grid md:grid-cols-2 gap-8">
{posts.map((post: any) => {
return (
<Link
key={post.id}
href={`/blog/${post.slug}`}
className="justify-between"
>
{post.featured_media ? (
<BlogImage imageID={post.featured_media} />
) : (
<div className="w-full h-64 bg-accent rounded-lg"></div>
)}
<h1 className="font-sans text-2xl font-bold my-4">
{post.title.rendered}
</h1>
</Link>
);
})}
</div>
</div>
</Internal>
);
}

View File

@@ -1,118 +1,25 @@
import Image from 'next/image'
import { Inter } from 'next/font/google'
import { Canvas } from "@react-three/fiber";
import { OrbitControls, Plane } from "@react-three/drei";
const inter = Inter({ subsets: ['latin'] })
import Internal from "@/components/common/Internal";
export default function Home() {
export default function Home(): JSX.Element {
return (
<main
className={`flex min-h-screen flex-col items-center justify-between p-24 ${inter.className}`}
<Internal>
<div className="absolute h-screen w-full top-0 left-0 scene">
<Canvas
shadows
camera={{ position: [4, 2, 2] }}
>
<div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
Get started by editing&nbsp;
<code className="font-mono font-bold">src/pages/index.tsx</code>
</p>
<div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none">
<a
className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
By{' '}
<Image
src="/vercel.svg"
alt="Vercel Logo"
className="dark:invert"
width={100}
height={24}
priority
<OrbitControls />
<ambientLight intensity={0.1} />
<directionalLight
color="white"
position={[0, 5, 5]}
/>
</a>
<Plane args={[2, 2]} />
</Canvas>
</div>
</div>
<div className="relative flex place-items-center before:absolute before:h-[300px] before:w-[480px] before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-[240px] after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700/10 after:dark:from-sky-900 after:dark:via-[#0141ff]/40 before:lg:h-[360px]">
<Image
className="relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert"
src="/next.svg"
alt="Next.js Logo"
width={180}
height={37}
priority
/>
</div>
<div className="mb-32 grid text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:grid-cols-4 lg:text-left">
<a
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className={`mb-3 text-2xl font-semibold`}>
Docs{' '}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
Find in-depth information about Next.js features and API.
</p>
</a>
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className={`mb-3 text-2xl font-semibold`}>
Learn{' '}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
Learn about Next.js in an interactive course with&nbsp;quizzes!
</p>
</a>
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className={`mb-3 text-2xl font-semibold`}>
Templates{' '}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
Discover and deploy boilerplate example Next.js&nbsp;projects.
</p>
</a>
<a
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className={`mb-3 text-2xl font-semibold`}>
Deploy{' '}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
Instantly deploy your Next.js site to a shareable URL with Vercel.
</p>
</a>
</div>
</main>
)
</Internal>
);
}

25
src/services/pages.ts Normal file
View File

@@ -0,0 +1,25 @@
import { WORDPRESS_BASE_API, WORDPRESS_POSTS_FIELD_FILTERS, WORDPRESS_POST_FIELD_FILTERS } from '@/utils/common-utils';
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import { HYDRATE } from 'next-redux-wrapper';
export const pagesApi: any = createApi({
reducerPath: 'pages',
baseQuery: fetchBaseQuery({ baseUrl: WORDPRESS_BASE_API }),
extractRehydrationInfo(action, { reducerPath }) {
if(action.type === HYDRATE) {
return action.payload[reducerPath]
}
},
endpoints: (builder) => ({
getPages: builder.query({
query: () => `/pages${WORDPRESS_POSTS_FIELD_FILTERS}`
}),
getPage: builder.query({
query: (slug) => `/pages${WORDPRESS_POST_FIELD_FILTERS}&slug=${slug}`
})
})
});
export const { useGetPagesQuery, useGetPageQuery, util: { getRunningQueriesThunk } } = pagesApi;
export const { getPages, getPage } = pagesApi.endpoints;

28
src/services/posts.ts Normal file
View File

@@ -0,0 +1,28 @@
import { WORDPRESS_BASE_API, WORDPRESS_POSTS_FIELD_FILTERS, WORDPRESS_POST_FIELD_FILTERS } from '@/utils/common-utils';
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import { HYDRATE } from 'next-redux-wrapper';
export const postsApi: any = createApi({
reducerPath: 'posts',
baseQuery: fetchBaseQuery({ baseUrl: WORDPRESS_BASE_API }),
extractRehydrationInfo(action, { reducerPath }) {
if(action.type === HYDRATE) {
return action.payload[reducerPath]
}
},
endpoints: (builder) => ({
getPostImage: builder.query({
query: (imageID) => `/media/${imageID}`
}),
getPosts: builder.query({
query: () => `/posts${WORDPRESS_POSTS_FIELD_FILTERS}`
}),
getPost: builder.query({
query: (slug) => `/posts${WORDPRESS_POST_FIELD_FILTERS}&slug=${slug}`
})
})
});
export const { useGetPostsQuery, useGetPostQuery, useGetPostImageQuery, util: { getRunningQueriesThunk } } = postsApi;
export const { getPosts, getPost, getPostImage } = postsApi.endpoints;

View File

@@ -2,26 +2,75 @@
@tailwind components;
@tailwind utilities;
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
@layer base {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 346.8 77.2% 49.8%;
--primary-foreground: 355.7 100% 97.3%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 346.8 77.2% 49.8%;
--radius: 0.5rem;
}
.dark {
--background: 0 0% 8%;
--foreground: 0 0% 95%;
--card: 24 9.8% 10%;
--card-foreground: 0 0% 95%;
--popover: 0 0% 9%;
--popover-foreground: 0 0% 95%;
--primary: 346.8 77.2% 49.8%;
--primary-foreground: 355.7 100% 97.3%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 15%;
--muted-foreground: 240 5% 64.9%;
--accent: 12 6.5% 15.1%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 85.7% 97.3%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 346.8 77.2% 49.8%;
}
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
@layer base {
* {
@apply border-border;
font-family: 'Roboto', 'Arial', sans-serif;
}
body {
@apply bg-background text-foreground;
}
}
@layer utilities {
.post-content h2.wp-block-heading {
@apply text-4xl font-bold mb-4 text-primary;
}
.post-content h3.wp-block-heading {
@apply text-2xl font-bold mb-4;
}
.post-content img, video {
@apply block w-full mb-10;
}
.post-content p {
@apply mb-4 text-lg text-foreground leading-8;
}
}

View File

@@ -0,0 +1,3 @@
export const WORDPRESS_BASE_API = 'https://www.frankdelaguila.com/wp-json/wp/v2';
export const WORDPRESS_POSTS_FIELD_FILTERS = '?_fields=id,slug,title,_links,featured_media';
export const WORDPRESS_POST_FIELD_FILTERS = '?_fields=id,slug,title,content,date,modified';

6
src/utils/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

View File

@@ -1,20 +1,80 @@
import type { Config } from 'tailwindcss'
const config: Config = {
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
theme: {
fontFamily: {
'roboto': ['Roboto', 'Arial', 'ui-sans-serif', 'system,ui', 'sans-serif'],
'sans': ['Ubuntu', 'ui-sans-serif', 'system,ui', 'sans-serif']
},
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
backgroundImage: {
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
'gradient-conic':
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: 0 },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [],
plugins: [require("tailwindcss-animate")],
}
export default config