Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@
// "next/babel",
"next/core-web-vitals",
"prettier"
]
],
"rules": {
"no-unused-vars": "warn"
}
}
18 changes: 6 additions & 12 deletions app/error.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
"use client";

import { useEffect } from "react";
import { Button, Flex, Text } from "@chakra-ui/react";

export default function Error({ error, reset }) {
useEffect(() => {
console.error(error);
}, [error]);

return (
<Flex gap={5} direction="column" alignItems="center">
<Text color="red.200">Something went wrong!</Text>
<Button onClick={() => reset()} variant="outline" colorScheme="red">
<div className="h-full w-full flex-center flex-col gap-5">
<p className="text-3xl text-red-500">Something went wrong! </p>
<p className="text-red-500">{error.toString()}</p>
<button className="btn" onClick={() => reset()}>
Retry
</Button>
</Flex>
</button>
</div>
);
}
24 changes: 8 additions & 16 deletions app/layout.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,20 @@
"use client";

import { Suspense } from "react";
import { ChakraProvider, Box } from "@chakra-ui/react";
import { AppHeader, AppFooter } from "components";
import { LayoutProvider } from "context/layout";
import { theme, navigationHeight, footerHeight } from "util/theme-config";
import { AppHeader, AppFooter, AppMetadata } from "components";
import Loading from "./loading";
import "styles/globals.css";
import { ThemeContext } from "context";

// export const metadata = { ...AppMetadata };
export const metadata = { ...AppMetadata };

export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<ChakraProvider theme={theme}>
<ThemeContext>
<AppHeader />

<LayoutProvider>
<Box as="main" minHeight={`calc(100vh - ${navigationHeight}px - ${footerHeight}px)`}>
<Suspense fallback={<Loading />}>{children}</Suspense>
</Box>
<AppFooter />
</LayoutProvider>
</ChakraProvider>
<Suspense fallback={<Loading />}>{children}</Suspense>
<AppFooter />
</ThemeContext>
</body>
</html>
);
Expand Down
8 changes: 5 additions & 3 deletions app/loading.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"use client";

import { Loader } from "components";

export default function Loading() {
return <Loader width="100%" />;
return (
<div className="flex-center">
<Loader textClassNames="text-2xl text-center" />
</div>
);
}
4 changes: 2 additions & 2 deletions app/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { WelcomeSection, AboutSection, TechnologiesSection, ProjectsSection } fr

export default function Page() {
return (
<>
<div className="container-md">
<WelcomeSection />
<AboutSection />
<ProjectsSection />
<TechnologiesSection />
</>
</div>
);
}
70 changes: 37 additions & 33 deletions app/projects/components/Filter.jsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,63 @@
import { useRef } from "react";
import { useRef, useState } from "react";
import { LazyMotion, domAnimation, useInView } from "framer-motion";
import { Flex, Button, Heading, HStack } from "@chakra-ui/react";
import { TbBrandJavascript, TbBrandNextjs } from "react-icons/tb";
import { FaReact } from "react-icons/fa";
import { FilterButton } from "./FilterButton";

export function Filter({ onClick = (f) => f }) {
const animRef = useRef(null);
const isInView = useInView(animRef, { once: true });
const [activeFilter, setActiveFilter] = useState(undefined);

const handleFilterClick = (filter) => {
onClick(filter);
setActiveFilter(filter);
};

return (
<LazyMotion features={domAnimation}>
<Flex
py={8}
gap={3}
alignItems="center"
<div
ref={animRef}
sx={{
className="flex items-start flex-col sm:flex-row sm:items-center gap-4 my-10"
style={{
opacity: isInView ? 1 : 0,
transition: "all 0.9s cubic-bezier(0.17, 0.55, 0.55, 1) 0.5s"
transition: "all 0.9s cubic-bezier(0.17, 0.55, 0.55, 1) 1s"
}}
>
<Heading as="h3" fontSize="xl" aria-label="Filter projects" tabIndex="0">
<h3 aria-label="Filter projects" tabIndex="0" className="font-bold text-xl">
Filter by:
</Heading>
<HStack>
<Button
variant="outline"
onClick={() => onClick(undefined)}
aria-label="Show all projects"
</h3>
<div className="flex items-center gap-4">
<FilterButton
onClick={() => handleFilterClick(undefined)}
label="All"
active={activeFilter === undefined}
>
All
</Button>
<Button
variant="outline"
onClick={() => onClick("React")}
aria-label="Filter projects by react"
</FilterButton>
<FilterButton
onClick={() => handleFilterClick("React")}
label="React"
active={activeFilter === "React"}
>
<FaReact size="20" />
</Button>
<Button
variant="outline"
onClick={() => onClick("Next")}
aria-label="Filter projects by next"
</FilterButton>
<FilterButton
onClick={() => handleFilterClick("Next")}
label="Next"
active={activeFilter === "Next"}
>
<TbBrandNextjs size="20" />
</Button>
<Button
variant="outline"
onClick={() => onClick("Javascript")}
aria-label="Filter projects by javascript"
</FilterButton>
<FilterButton
onClick={() => handleFilterClick("Javascript")}
label="Javascript"
active={activeFilter === "Javascript"}
>
<TbBrandJavascript size="20" />
</Button>
</HStack>
</Flex>
</FilterButton>
</div>
</div>
</LazyMotion>
);
}
12 changes: 12 additions & 0 deletions app/projects/components/FilterButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export function FilterButton({ onClick, label, active, children }) {
const activeClassName = "icon-link-btn--active";
return (
<button
className={`icon-link-btn icon-link-btn--outline w-14 h-10 ${active ? activeClassName : ""}`}
onClick={onClick}
aria-label={`Filter projects by ${label.toLowerCase()}`}
>
{children}
</button>
);
}
13 changes: 13 additions & 0 deletions app/projects/components/Projects.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ProjectItem } from "../../sections";

export function Projects({ projects }) {
return (
<div className="grid grid-cols-1 sm:grid-cols-3 gap-8">
{projects
?.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
?.map((project, index) => (
<ProjectItem key={project._id} project={project} index={index} />
))}
</div>
);
}
61 changes: 61 additions & 0 deletions app/projects/layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"use client";

import { Suspense, useState } from "react";
import useSWR from "swr";
import { ErrorBoundary } from "react-error-boundary";
import { HeadingDivider, Loader } from "components";
import { Filter } from "./components/Filter";
import { fetcher } from "utils/fetcher";
import Error from "../error";
import { Projects } from "./components/Projects";

const url = `${process.env.NEXT_PUBLIC_SANITY_URL}${process.env.NEXT_PUBLIC_SANITY_ALL_PROJECTS}`;

export default function Page() {
const [category, setCategory] = useState(undefined);
const filterUrl = `${process.env.NEXT_PUBLIC_SANITY_URL}${process.env.NEXT_PUBLIC_SANITY_PROJECTS}${category}${process.env.NEXT_PUBLIC_SANITY_PROJECT_BY_CATEGORY}`;

const fetchUrl = category ? filterUrl : url;
const { data, error } = useSWR(fetchUrl, fetcher);
const filteredProjects = data?.result;

const onClick = (catName) => setCategory(catName);

if (error) {
return <div className="container-md">Error loading projects...</div>;
}

return (
<div className="container-md">
<section id="projects" className="section">
<HeadingDivider title="Relevant projects" />

<Filter onClick={onClick} />

<Suspense
fallback={
<div className="flex-center">
<Loader />
</div>
}
>
<ErrorBoundary FallbackComponent={Error}>
{filteredProjects === undefined ? (
// Loading state
<div className="flex-center">
<Loader />
</div>
) : filteredProjects.length === 0 ? (
// Empty state
<div className="flex-center">
<h3 className="text-2xl">No projects found in {category} category</h3>
</div>
) : (
<Projects projects={filteredProjects} />
)}
</ErrorBoundary>
</Suspense>
</section>
</div>
);
}
45 changes: 5 additions & 40 deletions app/projects/page.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,7 @@
"use client";
export const metadata = {
title: `Projects`
};

import { useState } from "react";
import useSWR from "swr";
import { ErrorBoundary } from "react-error-boundary";
import { Box, SimpleGrid } from "@chakra-ui/react";
import { HeadingDivider } from "components";
import { ProjectItem } from "app/sections/project/ProjectItem";
import { Filter } from "./components/Filter";
import { fetcher } from "util/fetcher";
import Error from "../error";

const url = `${process.env.NEXT_PUBLIC_SANITY_URL}${process.env.NEXT_PUBLIC_SANITY_ALL_PROJECTS}`;

export default function Page() {
const [category, setCategory] = useState();
const filterUrl = `${process.env.NEXT_PUBLIC_SANITY_URL}?query=*%5B_type%20%3D%3D%20%22projects%22%20%26%26%20category%5B0%5D-%3Etitle%20%3D%3D%20%22${category}%22%5D%7B%0A%20%20_id%2C%0A%20%20createdAt%2C%0A%20%20%22category%22%3A%20category%5B0%5D-%3Etitle%2C%0A%20%20description%2C%0A%20%20title%2C%0A%20%20%22poster%22%3A%20poster.asset-%3Eurl%2C%0A%20%20liveUrl%2C%0A%20%20repoUrl%2C%0A%20%20%22images%22%3A%20images%5B%5D.asset-%3Eurl%2C%0A%20%20%22stack%22%3A%20stack%5B%5D-%3Etitle%0A%7D%20%7C%20order(createdAt%20desc)`;

const fetchUrl = category ? filterUrl : url;
const { data: filteredProjs } = useSWR(fetchUrl, fetcher);
const filteredProjects = filteredProjs?.result;

const onClick = (catName) => setCategory(catName);

return (
<Box as="section" id="projects" className="section">
<HeadingDivider title="Relevant projects" />

<Filter onClick={onClick} />

<ErrorBoundary FallbackComponent={Error}>
<SimpleGrid spacingY={10} spacingX={6} columns={[1, 1, 3]}>
{filteredProjects
?.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
?.map((project) => (
<ProjectItem key={project._id} project={project} />
))}
</SimpleGrid>
</ErrorBoundary>
</Box>
);
export default function Page({ children }) {
return <div className="container-md">{children}</div>;
}
Loading