Adding in changes
This commit is contained in:
16
components.json
Normal file
16
components.json
Normal 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
1666
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
25
package.json
25
package.json
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
21
src/components/common/BlogImage/BlogImage.tsx
Normal file
21
src/components/common/BlogImage/BlogImage.tsx
Normal 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;
|
||||
17
src/components/common/Internal.tsx
Normal file
17
src/components/common/Internal.tsx
Normal 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;
|
||||
43
src/components/common/Loading/index.tsx
Normal file
43
src/components/common/Loading/index.tsx
Normal 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;
|
||||
184
src/components/common/Navigation.tsx
Normal file
184
src/components/common/Navigation.tsx
Normal 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;
|
||||
17
src/components/common/scrollDown.tsx
Normal file
17
src/components/common/scrollDown.tsx
Normal 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;
|
||||
56
src/components/ui/button.tsx
Normal file
56
src/components/ui/button.tsx
Normal 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 };
|
||||
128
src/components/ui/navigation-menu.tsx
Normal file
128
src/components/ui/navigation-menu.tsx
Normal 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
147
src/components/ui/sheet.tsx
Normal 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,
|
||||
};
|
||||
15
src/components/ui/skeleton.tsx
Normal file
15
src/components/ui/skeleton.tsx
Normal 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
2
src/lib/redux/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './store';
|
||||
export * from './slices';
|
||||
10
src/lib/redux/middleware.ts
Normal file
10
src/lib/redux/middleware.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/* Core */
|
||||
import { pagesApi } from '@/services/pages'
|
||||
import { postsApi } from '@/services/posts'
|
||||
|
||||
const middleware = [
|
||||
postsApi.middleware,
|
||||
pagesApi.middleware,
|
||||
]
|
||||
|
||||
export { middleware }
|
||||
7
src/lib/redux/rootReducer.ts
Normal file
7
src/lib/redux/rootReducer.ts
Normal 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,
|
||||
};
|
||||
1
src/lib/redux/slices/index.ts
Normal file
1
src/lib/redux/slices/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './selectors';
|
||||
4
src/lib/redux/slices/selectors.ts
Normal file
4
src/lib/redux/slices/selectors.ts
Normal 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
30
src/lib/redux/store.ts
Normal 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
44
src/pages/[slug].tsx
Normal 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;
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
83
src/pages/blog/[slug]/index.tsx
Normal file
83
src/pages/blog/[slug]/index.tsx
Normal 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
48
src/pages/blog/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
<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">
|
||||
->
|
||||
</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">
|
||||
->
|
||||
</span>
|
||||
</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
||||
Learn about Next.js in an interactive course with 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">
|
||||
->
|
||||
</span>
|
||||
</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
||||
Discover and deploy boilerplate example Next.js 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">
|
||||
->
|
||||
</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
25
src/services/pages.ts
Normal 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
28
src/services/posts.ts
Normal 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;
|
||||
@@ -2,26 +2,75 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--foreground-rgb: 0, 0, 0;
|
||||
--background-start-rgb: 214, 219, 220;
|
||||
--background-end-rgb: 255, 255, 255;
|
||||
--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;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--foreground-rgb: 255, 255, 255;
|
||||
--background-start-rgb: 0, 0, 0;
|
||||
--background-end-rgb: 0, 0, 0;
|
||||
.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%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
font-family: 'Roboto', 'Arial', sans-serif;
|
||||
}
|
||||
body {
|
||||
color: rgb(var(--foreground-rgb));
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
rgb(var(--background-end-rgb))
|
||||
)
|
||||
rgb(var(--background-start-rgb));
|
||||
@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;
|
||||
}
|
||||
}
|
||||
3
src/utils/common-utils.ts
Normal file
3
src/utils/common-utils.ts
Normal 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
6
src/utils/utils.ts
Normal 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))
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user