Skip to content

Commit

Permalink
'use client';
Browse files Browse the repository at this point in the history
import dynamic from 'next/dynamic';
import 'swiper/css';
import 'swiper/css/free-mode';
import 'swiper/css/pagination';
import 'swiper/css/navigation';
import { sectionsData } from '@/constants/sections';
import { SectionsSchema, SectionsTypes } from '@/schemas';
import useSupabaseFetch from '@/hooks/useSupabaseFetch';
import { memo, useEffect } from 'react';
import filterByLanguage from '@/lib/utils/filterByLanguage';
import { getLocale } from '@/i18n/client';
import { useDispatch, useSelector } from 'react-redux';
import { setSectionItems } from '@/store/redux/sectionItems';
import { RootState } from '@/store';
import FsLoading from '@/components/Loading/FsLoading';

const Section = dynamic(() => import('@/components/Sections/Section'), {
  ssr: false,
  loading: () => <FsLoading />,
});

const Home = () => {
  const dispatch = useDispatch();
  const filteredData = useSelector((state: RootState) => state.section.items);
  const { data, loading, error } = useSupabaseFetch<SectionsTypes>(
    'home_schema',
    'sections',
    `*, translations(*, cards(*))`,
    SectionsSchema,
  );

  useEffect(() => {
    const fetchLocaleAndFilterData = async () => {
      const language = await getLocale();

      if (data && !loading) {
        const filteredItems = filterByLanguage({
          items: data,
          language,
          localPath: 'translations',
        });
        dispatch(setSectionItems(filteredItems));
      }
    };

    fetchLocaleAndFilterData();
  }, [data, dispatch, loading]);

  if (error) {
    console.error(error);
  }

  return !filteredData || filteredData.length <= 1 || error || loading ? (
    <FsLoading />
  ) : (
    <Section sectionsData={sectionsData} />
  );
};

export default memo(Home);
  • Loading branch information
0x74h51N committed Aug 26, 2024
1 parent 546e1bf commit ea0dfb2
Show file tree
Hide file tree
Showing 47 changed files with 302 additions and 179 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Introduction

This project began with the goal of creating a personal website to showcase my skills and portfolio. I have chosen "CrunchyPix" as the website and brand name. This website supports multiple languages through i18n and was built from scratch using Next.js 14. Although Tailwind CSS and DaisyUI are used for styling, the site also includes custom UI elements and components. Localization is handled both on the [server](/i18n/server.ts) and [client](/i18n/client.ts) sides[\*](/i18n/settings.ts), without relying on routing, by configuring i18n on the server to detect and set the language based on the user's browser or referrer information. Third-party services like Cloudinary, Supabase, and Vercel Analytics / SpeedInsight have been integrated, ensuring that Vercel Analytics or SpeedInsight are not activated without user consent, and no personal information is collected without permission.
This project began with the goal of creating a personal website to showcase my skills and portfolio. I have chosen "CrunchyPix" as the website and brand name. This website supports multiple languages through i18n and was built from scratch using Next.js 14. Although Tailwind CSS and DaisyUI are used for styling, the site also includes custom UI elements and components. Localization is handled both on the [server](/i18n/server.ts) and [client](/i18n/client.ts) sides[\*](/i18n/settings.ts). Third-party services like Cloudinary, Supabase, and Vercel Analytics / SpeedInsight have been integrated, ensuring that Vercel Analytics or SpeedInsight are not activated without user consent, and no personal information is collected without permission.

## 📦 Technology Stack

Expand Down
5 changes: 5 additions & 0 deletions app/[lang]/[...not_found]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { notFound } from 'next/navigation';

export default function NotFoundCatchAll() {
notFound();
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
21 changes: 16 additions & 5 deletions app/layout.tsx → app/[lang]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,31 @@ import { ArrowToTop } from '@/components/Buttons/ArrowToTop';
import AllRoutes from '@/components/RooteTitles/AllRoutes';
import CookieConsent from '@/components/Cookies/CookiesConsent';
import Cookies from '@/components/Cookies/Cookies';
import { getLocale } from '@/i18n/server';
import { generatePageMetadata } from '../lib/metadata';
import { generatePageMetadata } from '../../lib/metadata';
import PortfolioDataStore from '@/components/PortfolioDataStore';
import { Locales, supportedLocales } from '@/i18n/settings';
import { dir } from 'i18next';

const inter = Inter({ subsets: ['latin'] });
export async function generateMetadata(): Promise<Metadata> {
return generatePageMetadata('home');
}

const RootLayout = async ({ children }: { children: React.ReactNode }) => {
export function generateStaticParams() {
return supportedLocales.map((lang) => ({ lang }));
}

const RootLayout = async ({
children,
params: { lang },
}: {
children: React.ReactNode;
params: { lang: Locales };
}) => {
return (
<html lang={getLocale()}>
<html lang={lang} dir={dir(lang)}>
<body className="lg:overflow-x-hidden">
<AppI18nProvider>
<AppI18nProvider lang={lang}>
<AppReduxProvider>
<CustomCursor />
<CookieConsent />
Expand Down
2 changes: 1 addition & 1 deletion app/not-found.tsx → app/[lang]/not-found.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use client';
import { useTranslation } from '@/hooks/useTranslation';
import { CldImage } from 'next-cloudinary';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';

const Custom404 = () => {
const router = useRouter();
Expand Down
21 changes: 16 additions & 5 deletions app/page.tsx → app/[lang]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@ import 'swiper/css/navigation';
import { sectionsData } from '@/constants/sections';
import { SectionsSchema, SectionsTypes } from '@/schemas';
import useSupabaseFetch from '@/hooks/useSupabaseFetch';
import { memo, useEffect } from 'react';
import { memo, useEffect, useState } from 'react';
import filterByLanguage from '@/lib/utils/filterByLanguage';
import { getLocale } from '@/i18n/client';
import { useDispatch, useSelector } from 'react-redux';
import { setSectionItems } from '@/store/redux/sectionItems';
import { RootState } from '@/store';
import FsLoading from '@/components/Loading/FsLoading';
import { Locales } from '@/i18n/settings';

const Section = dynamic(() => import('@/components/Sections/Section'), {
ssr: false,
loading: () => <FsLoading />,
});

const Home = () => {
const Home = ({ params: { lang } }: { params: { lang: Locales } }) => {
const dispatch = useDispatch();
const filteredData = useSelector((state: RootState) => state.section.items);
const { data, loading, error } = useSupabaseFetch<SectionsTypes>(
Expand All @@ -29,9 +29,19 @@ const Home = () => {
`*, translations(*, cards(*))`,
SectionsSchema,
);
const language = getLocale();

const [language, setLanguage] = useState<string | null>(null);

useEffect(() => {
const fetchLanguage = () => {
setLanguage(lang);
};

fetchLanguage();
}, [lang]);

useEffect(() => {
if (data && !loading) {
if (language && data && !loading) {
const filteredItems = filterByLanguage({
items: data,
language,
Expand All @@ -44,6 +54,7 @@ const Home = () => {
if (error) {
console.error(error);
}

return !filteredData || filteredData.length <= 1 || error || loading ? (
<FsLoading />
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,28 @@ const PolicyCreator = ({ id }: { id: string }) => {
);

const [filteredData, setFilteredData] = useState<PoliciesTypes[]>([]);
const language = getLocale() || i18next.language;
const [language, setLanguage] = useState<string | null>(null);

useEffect(() => {
if (data) {
const fetchLanguage = async () => {
const locale = await getLocale();
setLanguage(locale || i18next.language);
};

fetchLanguage();
}, []);

useEffect(() => {
if (data && language) {
const filteredDat = filterByLanguage({
items: data,
language,
localPath: 'translations',
});
setFilteredData(filteredDat);
}
}, [data, language, setFilteredData]);
console.log(filteredData);
}, [data, language]);

useEffect(() => {
document.title = t('meta.title');
}, [t, language]);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import PolicyCreator from '@/app/policies/[id]/components/PolicyCreator';
import PolicyCreator from '@/app/[lang]/policies/[id]/components/PolicyCreator';
import { fetchSupabaseData } from '@/lib/utils/fetchSupabaseData';
import { PoliciesTypes, PoliciesSchema } from '@/schemas';
import { notFound } from 'next/navigation';
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
35 changes: 35 additions & 0 deletions app/actions/switch-locale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use server';

import { cookies } from 'next/headers';
import { Locales, NEXT_LOCALE, supportedLocales } from '@/i18n/settings';

export async function switchLocaleAction(value: Locales) {
if (supportedLocales.includes(value)) {
cookies().set(NEXT_LOCALE, value, {
path: '/',
maxAge: 365 * 24 * 60 * 60,
httpOnly: false,
sameSite: 'strict',
secure: process.env.NODE_ENV === 'production',
});

return {
status: 'success',
};
}

return {
status: 'error',
message: 'Unsupported locale',
};
}

export async function getLocaleCookie(): Promise<Locales | null> {
const response = cookies().get(NEXT_LOCALE);

if (response && supportedLocales.includes(response.value as Locales)) {
return response.value as Locales;
}

return null;
}
18 changes: 13 additions & 5 deletions app/robots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@ import type { MetadataRoute } from 'next';

export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: ['/private/', '/public', '/actions/', '/404', '/policies/*'],
},
rules: [
{
userAgent: '*',
allow: '/',
disallow: [
'/private/*',
'/public/*',
'/actions/*',
'/404',
'/policies/*',
],
},
],
sitemap: 'https://crunchypix.com/sitemap.xml',
};
}
35 changes: 30 additions & 5 deletions app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,43 @@ import { fetchSupabaseData } from '@/lib/utils/fetchSupabaseData';
import { PortfolioItemProps, PortfolioItemSchema } from '@/schemas';

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = 'https://crunchypix.com';
const staticUrls = [
{
url: 'https://crunchypix.com',
url: `${baseUrl}/en`,
lastModified: new Date(),
changeFrequency: 'monthly' as const,
priority: 1,
alternates: {
languages: {
de: `${baseUrl}/de`,
tr: `${baseUrl}/tr`,
},
},
},
{
url: 'https://crunchypix.com/portfolio',
url: `${baseUrl}/en/portfolio`,
lastModified: new Date(),
changeFrequency: 'weekly' as const,
priority: 0.85,
alternates: {
languages: {
de: `${baseUrl}/de/portfolio`,
tr: `${baseUrl}/tr/portfolio`,
},
},
},
{
url: 'https://crunchypix.com/about',
url: `${baseUrl}/en/about`,
lastModified: new Date(),
changeFrequency: 'monthly' as const,
priority: 0.5,
alternates: {
languages: {
de: `${baseUrl}/de/about`,
tr: `${baseUrl}/tr/about`,
},
},
},
];

Expand All @@ -32,10 +51,16 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
);

const dynamicUrls = portfolioItems.map((item) => ({
url: `https://crunchypix.com/portfolio/${item._id}`,
url: `${baseUrl}/en/portfolio/${item._id}`,
lastModified: new Date(),
changeFrequency: 'monthly' as const,
priority: 0.8,
priority: 0.75,
alternates: {
languages: {
de: `${baseUrl}/de/portfolio/${item._id}`,
tr: `${baseUrl}/tr/portfolio/${item._id}`,
},
},
}));

return [...staticUrls, ...dynamicUrls];
Expand Down
2 changes: 1 addition & 1 deletion components/Cookies/CookiesConsent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import ReactMarkdown from 'react-markdown';
import CustomLink from '../CustomLink';
import { motion } from 'framer-motion';
import { fadeIn } from '@/utils/motion';
import { useTranslation } from '@/hooks/useTranslation';
import useClickableHandlers from '@/hooks/useClickableHandlers';
import {
getCookieConsent,
setCookiesConsent,
} from '@/app/actions/setCookiesConsent';
import { useTranslation } from 'react-i18next';

const CookieConsent = () => {
const { t } = useTranslation('index');
Expand Down
2 changes: 1 addition & 1 deletion components/Footer/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import IconButton from '../Buttons/IconButton';
import { footerLinks } from '@/constants';
import FooterColumn from './FooterColumn';
import Contact from './Contact';
import { useTranslation } from '@/hooks/useTranslation';
import { IconProps, IconSchema } from '@/schemas';
import { useMemo } from 'react';
import useSupabaseFetch from '@/hooks/useSupabaseFetch';
import { useTranslation } from 'react-i18next';

const Footer = () => {
const { t } = useTranslation('index');
Expand Down
16 changes: 13 additions & 3 deletions components/Navbar/LanguageMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
'use client';
import i18n from '@/i18n/client';
import { DE, TR, GB } from 'country-flag-icons/react/3x2';
import { switchLocaleAction } from '@/i18n/actions/switch-locale';
import { switchLocaleAction } from '@/app/actions/switch-locale';
import Dropdown from '../Buttons/Dropdown';
import { useEffect, useState } from 'react';
import { Locales } from '@/i18n/settings';
import { usePathname, useRouter } from 'next/navigation';

const languages = [
{
Expand All @@ -19,18 +21,26 @@ const languages = [

const LanguageMenu = ({ smallNav }: { smallNav: boolean }) => {
const [selectedLanguage, setSelectedLanguage] = useState<string>('');
const router = useRouter();
const currentPathname = usePathname();

useEffect(() => {
const handleChange = async (selectedLanguage: string) => {
const result = await switchLocaleAction(selectedLanguage);
const result = await switchLocaleAction(selectedLanguage as Locales);
if (result.status === 'success') {
const newPath = currentPathname.replace(
`/${i18n.language}`,
`/${selectedLanguage}`,
);
router.replace(newPath);

i18n.changeLanguage(selectedLanguage);
}
};
if (selectedLanguage) {
handleChange(selectedLanguage);
}
}, [selectedLanguage]);
}, [selectedLanguage, currentPathname]);

Check warning on line 43 in components/Navbar/LanguageMenu.tsx

View workflow job for this annotation

GitHub Actions / Analyze (javascript)

React Hook useEffect has a missing dependency: 'router'. Either include it or remove the dependency array

Check warning on line 43 in components/Navbar/LanguageMenu.tsx

View workflow job for this annotation

GitHub Actions / Analyze (typescript)

React Hook useEffect has a missing dependency: 'router'. Either include it or remove the dependency array

Check warning on line 43 in components/Navbar/LanguageMenu.tsx

View workflow job for this annotation

GitHub Actions / Analyze (javascript)

React Hook useEffect has a missing dependency: 'router'. Either include it or remove the dependency array

Check warning on line 43 in components/Navbar/LanguageMenu.tsx

View workflow job for this annotation

GitHub Actions / Analyze (typescript)

React Hook useEffect has a missing dependency: 'router'. Either include it or remove the dependency array

const getFlagComponent = (language: string) => {
switch (language) {
Expand Down
2 changes: 1 addition & 1 deletion components/Navbar/MobileMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import BurgerButton from '../Buttons/BurgerButton';
import LanguageMenu from './LanguageMenu';
import { RootState } from '@/store';
import { useSelector } from 'react-redux';
import { useTranslation } from '@/hooks/useTranslation';
import { useOutsideClick } from '@/hooks/useOutsideClick';
import { useTranslation } from 'react-i18next';

type MobileMenuProps = {
smallNav: boolean;
Expand Down
2 changes: 1 addition & 1 deletion components/Navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import LanguageMenu from './LanguageMenu';
import { useSelector } from 'react-redux';
import { RootState } from '@/store';
import CrunchyLogo from './CrunchyLogo';
import { useTranslation } from '@/hooks/useTranslation';
import useClickableHandlers from '@/hooks/useClickableHandlers';
import useIntersectionObserver from '@/hooks/useIntersectionObserver';
import { useTranslation } from 'react-i18next';

const Navbar = () => {
const [isMenuOpen, setMobileMenu] = useState(false);
Expand Down
6 changes: 3 additions & 3 deletions components/RooteTitles/AllRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ const AllRoutes = () => {
useEffect(() => {
const updatePageInfo = async () => {
const urlParts = pathname.split('/');
const currentPage = urlParts[1];
const currentChildPage = urlParts[2] || '';
const currentPage = urlParts[2];
const currentChildPage = urlParts[3] || '';

setChildPage(currentChildPage);
setMainPage(currentPage);
Expand All @@ -41,7 +41,7 @@ const AllRoutes = () => {
};

const urlParts = pathname.split('/');
urlParts.length > 3 && setHasGrand(true);
urlParts.length > 4 && setHasGrand(true);
window.addEventListener('popstate', handlePopState);

return () => {
Expand Down
2 changes: 1 addition & 1 deletion components/RooteTitles/MainRoutes.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import useClickableHandlers from '@/hooks/useClickableHandlers';
import { useTranslation } from '@/hooks/useTranslation';
import { RootState } from '@/store';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';

const MainRoutes = ({
Expand Down
Loading

0 comments on commit ea0dfb2

Please sign in to comment.