├── .prettierignore
├── .eslintignore
├── public
├── bg.gif
├── favicon.ico
├── background.gif
├── favicon-16x16.png
├── favicon-32x32.png
├── apple-touch-icon.png
├── profile-picture.jpeg
├── resume
│ └── resume-en.pdf
├── projectMedia
│ └── loaner.png
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── site.webmanifest
├── browserconfig.xml
├── robots.txt
└── hero-graphic.svg
├── .prettierrc
├── next-env.d.ts
├── next.config.js
├── components
├── ui
│ └── ShortCenteredDivider.tsx
├── AnimatedLink.tsx
├── layout
│ ├── Footer.tsx
│ ├── NavigationDrawer.tsx
│ └── Navbar.tsx
├── styles
│ └── theme.ts
├── home
│ ├── constants
│ │ ├── skillIcons.tsx
│ │ └── socialIcons.tsx
│ ├── HeroSection.tsx
│ ├── AboutSection.tsx
│ ├── ContactSection.tsx
│ └── PortfolioSection.tsx
├── RecentPostCard.tsx
└── Link.tsx
├── .gitignore
├── tsconfig.json
├── utils
├── createEmotionCache.ts
└── getDataUrlWithShimmerEffect.ts
├── README.md
├── .eslintrc.json
├── package.json
├── pages
├── _app.tsx
├── index.tsx
└── _document.tsx
└── locales
└── en.json
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .next
3 | out
4 | package-lock.json
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | pages/_document.tsx
3 | components/Link.tsx
--------------------------------------------------------------------------------
/public/bg.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hash-debug/Portfolio/master/public/bg.gif
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "useTabs": false,
4 | "singleQuote": true
5 | }
6 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hash-debug/Portfolio/master/public/favicon.ico
--------------------------------------------------------------------------------
/public/background.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hash-debug/Portfolio/master/public/background.gif
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hash-debug/Portfolio/master/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hash-debug/Portfolio/master/public/favicon-32x32.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hash-debug/Portfolio/master/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/profile-picture.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hash-debug/Portfolio/master/public/profile-picture.jpeg
--------------------------------------------------------------------------------
/public/resume/resume-en.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hash-debug/Portfolio/master/public/resume/resume-en.pdf
--------------------------------------------------------------------------------
/public/projectMedia/loaner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hash-debug/Portfolio/master/public/projectMedia/loaner.png
--------------------------------------------------------------------------------
/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hash-debug/Portfolio/master/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hash-debug/Portfolio/master/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/public/site.webmanifest:
--------------------------------------------------------------------------------
1 | {"name":"Hrithika","short_name":"HR","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
--------------------------------------------------------------------------------
/public/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #ffffff
7 |
8 |
9 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | swcMinify: true,
5 | i18n: {
6 | locales: ['en', 'es'],
7 | defaultLocale: 'en',
8 | },
9 | images: {
10 | domains: ['res.cloudinary.com'],
11 | },
12 | };
13 |
14 | module.exports = nextConfig;
15 |
--------------------------------------------------------------------------------
/components/ui/ShortCenteredDivider.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '@mui/material/styles';
2 | import { Divider } from '@mui/material';
3 |
4 | const ShortCenteredDivider = styled(Divider)(({ theme }) => ({
5 | height: '4px',
6 | width: '60px',
7 | backgroundColor: theme.palette.primary.main,
8 | margin: 'auto',
9 | }));
10 |
11 | export default ShortCenteredDivider;
12 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | This favicon was generated using the following font:
2 |
3 | - Font Title: Lusitana
4 | - Font Author: Copyright (c) 2011 by Ana Paula Megda (www.anamegda.com|anapbm@gmail.com), with Reserved Font Name Lusitana.
5 | - Font Source: http://fonts.gstatic.com/s/lusitana/v13/CSR84z9ShvucWzsMKxhaRuMiSct_.ttf
6 | - Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL))
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "jsxImportSource": "@emotion/react",
17 | "incremental": true
18 | },
19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
20 | "exclude": ["node_modules"]
21 | }
22 |
--------------------------------------------------------------------------------
/components/AnimatedLink.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '@mui/material/styles';
2 |
3 | import Link from './Link';
4 |
5 | const AnimatedLink = styled(Link)(() => ({
6 | padding: '12px 15px',
7 | textDecoration: 'none',
8 | position: 'relative',
9 | '&::before': {
10 | content: '""',
11 | position: 'absolute',
12 | width: 0,
13 | height: '2px',
14 | bottom: 0,
15 | left: 0,
16 | backgroundColor: '#f50057',
17 | visibility: 'hidden',
18 | transition: 'all 0.3s ease-in-out',
19 | },
20 | '&:hover::before': {
21 | visibility: 'visible',
22 | width: '100%',
23 | },
24 | }));
25 |
26 | export default AnimatedLink;
27 |
--------------------------------------------------------------------------------
/utils/createEmotionCache.ts:
--------------------------------------------------------------------------------
1 | import createCache from '@emotion/cache';
2 |
3 | const isBrowser = typeof document !== 'undefined';
4 |
5 | // On the client side, Create a meta tag at the top of the
and set it as insertionPoint.
6 | // This assures that MUI styles are loaded first.
7 | // It allows developers to easily override MUI styles with other styling solutions, like CSS modules.
8 | export default function createEmotionCache() {
9 | let insertionPoint;
10 |
11 | if (isBrowser) {
12 | const emotionInsertionPoint = document.querySelector(
13 | 'meta[name="emotion-insertion-point"]'
14 | );
15 | insertionPoint = emotionInsertionPoint ?? undefined;
16 | }
17 |
18 | return createCache({ key: 'mui-style', insertionPoint });
19 | }
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## View project
2 |
3 | [Live Demo](https://hrithikasportfolio.netlify.app)
4 |
5 | ## About the project
6 |
7 | A personal portfolio website that showcases my feature projects, technical skill stack, contact information and more about me.
8 |
9 | ## Core dependencies
10 |
11 | - [React](https://reactjs.org/) - A Javascript library for building user interfaces.
12 | - [NextJS](https://nextjs.org/) - A React framework with hybrid static & server rendering, TypeScript support, smart bundling, route pre-fetching, and more.
13 | - [Material UI](https://material-ui.com/) - A React component-based design system.
14 | - [Yup](https://www.npmjs.com/package/yup) - A JavaScript schema builder for value parsing and validation.
15 | - [React Icons](https://www.npmjs.com/package/react-icons) - Include popular icons in your React projects.
16 |
--------------------------------------------------------------------------------
/utils/getDataUrlWithShimmerEffect.ts:
--------------------------------------------------------------------------------
1 | const shimmer = (w: number, h: number) => `
2 | `;
14 |
15 | const toBase64 = (str: string) =>
16 | typeof window === 'undefined'
17 | ? Buffer.from(str).toString('base64')
18 | : window.btoa(str);
19 |
20 | export default function getDataUrlWithShimmerEffect(w: number, h: number) {
21 | return `data:image/svg+xml;base64,${toBase64(shimmer(w, h))}`;
22 | }
23 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["next/core-web-vitals", "prettier"],
3 | "rules": {
4 | "react/react-in-jsx-scope": 0,
5 | "react/jsx-filename-extension": [
6 | 1,
7 | { "extensions": [".js", ".jsx", "ts", "tsx"] }
8 | ],
9 | "react/jsx-props-no-spreading": [1, { "custom": "ignore" }],
10 | "react/display-name": 1,
11 | "react/prop-types": 0,
12 | "jsx-a11y/anchor-is-valid": 0,
13 | "no-use-before-define": "off",
14 | "import/extensions": [
15 | "error",
16 | "ignorePackages",
17 | {
18 | "js": "never",
19 | "jsx": "never",
20 | "ts": "never",
21 | "tsx": "never"
22 | }
23 | ],
24 | "import/order": ["warn", { "newlines-between": "always" }],
25 | "react/jsx-sort-props": [
26 | "warn",
27 | {
28 | "callbacksLast": true,
29 | "shorthandFirst": true,
30 | "noSortAlphabetically": false,
31 | "reservedFirst": true
32 | }
33 | ]
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/components/layout/Footer.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Fab, Typography } from '@mui/material/';
2 | import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
3 |
4 | import { NextLinkComposed } from '../Link';
5 |
6 | export default function Footer() {
7 | return (
8 | theme.palette.grey[900] }}
11 | >
12 |
13 |
22 |
23 |
24 |
25 |
26 | Made with ❤️ by Hrithika {new Date().getFullYear()}
27 |
28 |
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/components/styles/theme.ts:
--------------------------------------------------------------------------------
1 | import { Roboto } from '@next/font/google';
2 | import { createTheme, responsiveFontSizes } from '@mui/material';
3 | import { cyan, pink } from '@mui/material/colors';
4 |
5 | export const roboto = Roboto({
6 | weight: ['300', '400', '500', '700'],
7 | subsets: ['latin'],
8 | display: 'swap',
9 | fallback: ['Helvetica', 'Arial', 'sans-serif'],
10 | });
11 |
12 | // Create a theme instance.
13 | const theme = responsiveFontSizes(
14 | createTheme({
15 | palette: {
16 | mode: 'dark',
17 | primary: pink,
18 | secondary: pink,
19 | error: {
20 | main: '#ff6358',
21 | },
22 | },
23 | typography: {
24 | fontFamily: roboto.style.fontFamily,
25 | },
26 | breakpoints: {
27 | values: {
28 | xs: 0,
29 | sm: 600,
30 | md: 960,
31 | lg: 1280,
32 | xl: 1920,
33 | },
34 | },
35 | components: {
36 | MuiCssBaseline: {
37 | styleOverrides: `
38 | html {
39 | scroll-behavior: smooth;
40 | }
41 | `,
42 | },
43 | },
44 | })
45 | );
46 |
47 | export default theme;
48 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nextjs-portfolio",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "type-check": "tsc --pretty --noEmit",
10 | "lint": "next lint",
11 | "format": "prettier --write \"./**/*.{js,jsx,ts,tsx}\""
12 | },
13 | "dependencies": {
14 | "@emotion/cache": "^11.10.5",
15 | "@emotion/react": "^11.10.5",
16 | "@emotion/server": "^11.10.0",
17 | "@emotion/styled": "^11.10.5",
18 | "@mui/icons-material": "^5.10.9",
19 | "@mui/material": "^5.10.13",
20 | "@next/font": "^13.0.2",
21 | "@sendgrid/mail": "^7.4.6",
22 | "clsx": "^1.2.1",
23 | "formik": "^2.2.9",
24 | "next": "^13.0.2",
25 | "react": "^18.2.0",
26 | "react-dom": "^18.2.0",
27 | "react-icons": "^4.6.0",
28 | "yup": "^0.32.11"
29 | },
30 | "devDependencies": {
31 | "@types/node": "^18.11.9",
32 | "@types/react": "^18.0.25",
33 | "@types/react-dom": "18.0.8",
34 | "eslint": "8.27.0",
35 | "eslint-config-next": "13.0.2",
36 | "eslint-config-prettier": "^8.5.0",
37 | "prettier": "^2.7.1",
38 | "typescript": "^4.8.4"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import type { AppProps } from 'next/app';
2 | import Head from 'next/head';
3 | import { CacheProvider, EmotionCache } from '@emotion/react';
4 | import { ThemeProvider } from '@mui/material/styles';
5 | import CssBaseline from '@mui/material/CssBaseline';
6 |
7 | import createEmotionCache from '../utils/createEmotionCache';
8 | import theme from '../components/styles/theme';
9 | import Navbar from '../components/layout/Navbar';
10 | import Footer from '../components/layout/Footer';
11 |
12 | // Client-side cache, shared for the whole session of the user in the browser.
13 | const clientSideEmotionCache = createEmotionCache();
14 |
15 | interface MyAppProps extends AppProps {
16 | // eslint-disable-next-line react/require-default-props
17 | emotionCache?: EmotionCache;
18 | }
19 |
20 | export default function MyApp(props: MyAppProps) {
21 | const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
22 |
23 | return (
24 |
25 |
26 | Hrithika R Portfolio
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/components/home/constants/skillIcons.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | SiHtml5,
3 | SiCss3,
4 | SiJavascript,
5 | SiGit,
6 | SiNodedotjs,
7 | SiReact,
8 | SiMongodb,
9 | SiMaterialui,
10 | SiBootstrap,
11 | SiNextdotjs,
12 | SiPostgresql,
13 | SiFirebase,
14 | SiTypescript,
15 | } from 'react-icons/si';
16 |
17 | const skillLogos = [
18 | {
19 | icon: ,
20 | label: 'Html5',
21 | },
22 | {
23 | icon: ,
24 | label: 'CSS3',
25 | },
26 | {
27 | icon: ,
28 | label: 'JavaScript',
29 | },
30 | {
31 | icon: ,
32 | label: 'TypeScript',
33 | },
34 | {
35 | icon: ,
36 | label: 'Git',
37 | },
38 | {
39 | icon: ,
40 | label: 'React',
41 | },
42 |
43 | {
44 | icon: ,
45 | label: 'NextJS',
46 | },
47 | {
48 | icon: ,
49 | label: 'Node',
50 | },
51 | {
52 | icon: ,
53 | label: 'PostgreSQL',
54 | },
55 | {
56 | icon: ,
57 | label: 'MongoDB',
58 | },
59 | {
60 | icon: ,
61 | label: 'Firebase',
62 | },
63 |
64 | {
65 | icon: ,
66 | label: 'Material UI',
67 | },
68 | {
69 | icon: ,
70 | label: 'Bootstrap',
71 | },
72 | ];
73 |
74 | export default skillLogos;
75 |
--------------------------------------------------------------------------------
/components/layout/NavigationDrawer.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | List,
4 | ListItem,
5 | ListItemIcon,
6 | ListItemText,
7 | Drawer,
8 | IconButton,
9 | Divider,
10 | } from '@mui/material';
11 | import CloseIcon from '@mui/icons-material/Close';
12 |
13 | import Link from '../Link';
14 |
15 | interface MenuItems {
16 | link: string;
17 | name: string;
18 | icon: JSX.Element;
19 | }
20 |
21 | interface NavigationDrawerProps {
22 | menuItems: MenuItems[];
23 | open: boolean;
24 | onClose: () => void;
25 | }
26 |
27 | export default function NavigationDrawer(props: NavigationDrawerProps) {
28 | const { menuItems, open, onClose } = props;
29 |
30 | return (
31 |
32 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | {menuItems.map((item) => (
49 |
55 | {item.icon}
56 |
57 |
58 | ))}
59 |
60 |
61 | );
62 | }
63 |
--------------------------------------------------------------------------------
/components/RecentPostCard.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/legacy/image';
2 | import {
3 | Box,
4 | Typography,
5 | Card,
6 | CardContent,
7 | CardActionArea,
8 | } from '@mui/material/';
9 |
10 | import getDataUrlWithShimmerEffect from '../utils/getDataUrlWithShimmerEffect';
11 |
12 | interface RecentPostCardProps {
13 | title: string;
14 | category: string;
15 | link: string;
16 | mediaSrc: string;
17 | }
18 |
19 | export default function RecentPostCard({
20 | title,
21 | category,
22 | link,
23 | mediaSrc,
24 | }: RecentPostCardProps) {
25 | return (
26 |
27 |
39 |
48 |
58 |
59 |
60 |
61 |
62 | {`#${category}`}
63 |
64 |
65 | {title}
66 |
67 |
68 |
69 |
70 | );
71 | }
72 |
--------------------------------------------------------------------------------
/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import { GetStaticProps } from 'next';
3 |
4 | import HeroSection from '../components/home/HeroSection';
5 | import AboutSection from '../components/home/AboutSection';
6 | import PortfolioSection from '../components/home/PortfolioSection';
7 | import ContactSection from '../components/home/ContactSection';
8 |
9 | export const getStaticProps: GetStaticProps = async ({ locale }) => {
10 | const response = await import(`../locales/${locale}.json`);
11 |
12 | return {
13 | props: {
14 | content: response.default,
15 | },
16 | };
17 | };
18 |
19 | export default function Index({
20 | content,
21 | }: {
22 | content: typeof import('../locales/en.json');
23 | }) {
24 | const {
25 | defaultSeo,
26 | heroData,
27 | aboutData,
28 | portfolioData,
29 | contactData,
30 | } = content;
31 |
32 | const { title, description, url, previewImage } = defaultSeo;
33 |
34 | return (
35 | <>
36 |
37 | {title}
38 |
39 |
43 |
44 |
45 |
46 |
47 | {/* Open Graph */}
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | >
61 | );
62 | }
63 |
--------------------------------------------------------------------------------
/components/home/HeroSection.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Container, Button, Typography, Grid } from '@mui/material/';
2 |
3 | interface HeroData {
4 | greetings: string;
5 | introduction: string;
6 | role: string;
7 | paragraph: string;
8 | button1: string;
9 | button2: string;
10 | }
11 |
12 | export default function Hero({ heroData: t }: { heroData: HeroData }) {
13 | return (
14 |
15 |
32 |
33 |
34 |
35 | {t.greetings}
36 |
37 | {t.introduction}
38 |
39 |
40 |
41 | {t.role}
42 |
43 |
44 |
45 | {t.paragraph}
46 |
47 |
48 |
49 |
50 |
51 |
59 |
60 |
61 |
69 |
70 |
71 |
72 |
73 | );
74 | }
75 |
--------------------------------------------------------------------------------
/components/home/constants/socialIcons.tsx:
--------------------------------------------------------------------------------
1 | const socialIcons = [
2 | {
3 | icon: (
4 |
14 | ),
15 | label: 'Github',
16 | href: 'https://github.com/Hash_Debug',
17 | },
18 | {
19 | icon: (
20 |
30 | ),
31 | label: 'LinkedIn',
32 | href: 'https://www.linkedin.com/in/hrithika-r-4451a6253/',
33 | },
34 | {
35 | icon: (
36 |
46 | ),
47 | label: 'Twitter',
48 | href: 'https://www.twitter.com/',
49 | },
50 | ];
51 |
52 | export default socialIcons;
53 |
--------------------------------------------------------------------------------
/components/layout/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import {
3 | AppBar,
4 | Toolbar,
5 | useScrollTrigger,
6 | Slide,
7 | Typography,
8 | IconButton,
9 | Container,
10 | } from '@mui/material/';
11 | import {
12 | Menu as MenuIcon,
13 | Home as HomeIcon,
14 | Work as WorkIcon,
15 | Mail as MailIcon,
16 | PermIdentity as PermIdentityIcon,
17 | } from '@mui/icons-material';
18 | import { useRouter } from 'next/router';
19 |
20 | import Link from '../Link';
21 | import AnimatedLink from '../AnimatedLink';
22 |
23 | import NavigationDrawer from './NavigationDrawer';
24 |
25 | interface Props {
26 | children: React.ReactElement;
27 | }
28 |
29 | function HideOnScroll(props: Props) {
30 | const { children } = props;
31 | const trigger = useScrollTrigger();
32 |
33 | return (
34 |
35 | {children}
36 |
37 | );
38 | }
39 |
40 | export default function ElevateAppBar() {
41 | const router = useRouter();
42 |
43 | const [mobileOpen, setMobileOpen] = useState(false);
44 | const handleDrawerToggle = () => {
45 | setMobileOpen(!mobileOpen);
46 | };
47 |
48 | const menuItems = [
49 | {
50 | link: '/#',
51 | name: `${router.locale === 'en' ? 'HOME' : 'INICIO'}`,
52 | icon: ,
53 | },
54 | {
55 | link: '/#about',
56 | name: `${router.locale === 'en' ? 'ABOUT' : 'ACERCA DE MI'}`,
57 | icon: ,
58 | },
59 | {
60 | link: '/#portfolio',
61 | name: `${router.locale === 'en' ? 'PORTFOLIO' : 'PORTAFOLIO'}`,
62 | icon: ,
63 | },
64 |
65 | {
66 | link: '/#contact',
67 | name: `${router.locale === 'en' ? 'CONTACT' : 'CONTACTO'}`,
68 | icon: ,
69 | },
70 | ];
71 |
72 | return (
73 |
121 | );
122 | }
123 |
--------------------------------------------------------------------------------
/components/Link.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import clsx from 'clsx';
3 | import { useRouter } from 'next/router';
4 | import NextLink, { LinkProps as NextLinkProps } from 'next/link';
5 | import MuiLink, { LinkProps as MuiLinkProps } from '@mui/material/Link';
6 | import { styled } from '@mui/material/styles';
7 |
8 | // Add support for the sx prop for consistency with the other branches.
9 | const Anchor = styled('a')({});
10 |
11 | interface NextLinkComposedProps
12 | extends Omit, 'href'>,
13 | Omit<
14 | NextLinkProps,
15 | 'href' | 'as' | 'onClick' | 'onMouseEnter' | 'onTouchStart'
16 | > {
17 | to: NextLinkProps['href'];
18 | linkAs?: NextLinkProps['as'];
19 | }
20 |
21 | export const NextLinkComposed = React.forwardRef<
22 | HTMLAnchorElement,
23 | NextLinkComposedProps
24 | >(function NextLinkComposed(props, ref) {
25 | const {
26 | to,
27 | linkAs,
28 | replace,
29 | scroll,
30 | shallow,
31 | prefetch,
32 | legacyBehavior = true,
33 | locale,
34 | ...other
35 | } = props;
36 |
37 | return (
38 |
49 |
50 |
51 | );
52 | });
53 |
54 | export type LinkProps = {
55 | activeClassName?: string;
56 | as?: NextLinkProps['as'];
57 | href: NextLinkProps['href'];
58 | linkAs?: NextLinkProps['as']; // Useful when the as prop is shallow by styled().
59 | noLinkStyle?: boolean;
60 | } & Omit &
61 | Omit;
62 |
63 | // A styled version of the Next.js Link component:
64 | // https://nextjs.org/docs/api-reference/next/link
65 | const Link = React.forwardRef(function Link(
66 | props,
67 | ref
68 | ) {
69 | const {
70 | activeClassName = 'active',
71 | as,
72 | className: classNameProps,
73 | href,
74 | legacyBehavior,
75 | linkAs: linkAsProp,
76 | locale,
77 | noLinkStyle,
78 | prefetch,
79 | replace,
80 | role, // Link don't have roles.
81 | scroll,
82 | shallow,
83 | ...other
84 | } = props;
85 |
86 | const router = useRouter();
87 | const pathname = typeof href === 'string' ? href : href.pathname;
88 | const className = clsx(classNameProps, {
89 | [activeClassName]: router.pathname === pathname && activeClassName,
90 | });
91 |
92 | const isExternal =
93 | typeof href === 'string' &&
94 | (href.indexOf('http') === 0 || href.indexOf('mailto:') === 0);
95 |
96 | if (isExternal) {
97 | if (noLinkStyle) {
98 | return ;
99 | }
100 |
101 | return ;
102 | }
103 |
104 | const linkAs = linkAsProp || as;
105 | const nextjsProps = {
106 | to: href,
107 | linkAs,
108 | replace,
109 | scroll,
110 | shallow,
111 | prefetch,
112 | legacyBehavior,
113 | locale,
114 | };
115 |
116 | if (noLinkStyle) {
117 | return (
118 |
124 | );
125 | }
126 |
127 | return (
128 |
135 | );
136 | });
137 |
138 | export default Link;
139 |
--------------------------------------------------------------------------------
/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Document, { Html, Head, Main, NextScript } from 'next/document';
3 | import createEmotionServer from '@emotion/server/create-instance';
4 |
5 | import theme, { roboto } from '../components/styles/theme';
6 | import createEmotionCache from '../utils/createEmotionCache';
7 |
8 | export default class MyDocument extends Document {
9 | render() {
10 | return (
11 |
12 |
13 |
17 | {/* PWA primary color */}
18 |
19 |
20 | {(this.props as any).emotionStyleTags}
21 |
26 |
32 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | );
49 | }
50 | }
51 |
52 | // `getInitialProps` belongs to `_document` (instead of `_app`),
53 | // it's compatible with static-site generation (SSG).
54 | MyDocument.getInitialProps = async (ctx) => {
55 | // Resolution order
56 | //
57 | // On the server:
58 | // 1. app.getInitialProps
59 | // 2. page.getInitialProps
60 | // 3. document.getInitialProps
61 | // 4. app.render
62 | // 5. page.render
63 | // 6. document.render
64 | //
65 | // On the server with error:
66 | // 1. document.getInitialProps
67 | // 2. app.render
68 | // 3. page.render
69 | // 4. document.render
70 | //
71 | // On the client
72 | // 1. app.getInitialProps
73 | // 2. page.getInitialProps
74 | // 3. app.render
75 | // 4. page.render
76 |
77 | const originalRenderPage = ctx.renderPage;
78 |
79 | // You can consider sharing the same Emotion cache between all the SSR requests to speed up performance.
80 | // However, be aware that it can have global side effects.
81 | const cache = createEmotionCache();
82 | const { extractCriticalToChunks } = createEmotionServer(cache);
83 |
84 | ctx.renderPage = () =>
85 | originalRenderPage({
86 | enhanceApp: (App: any) =>
87 | function EnhanceApp(props) {
88 | return ;
89 | },
90 | });
91 |
92 | const initialProps = await Document.getInitialProps(ctx);
93 | // This is important. It prevents Emotion to render invalid HTML.
94 | // See https://github.com/mui/material-ui/issues/26561#issuecomment-855286153
95 | const emotionStyles = extractCriticalToChunks(initialProps.html);
96 | const emotionStyleTags = emotionStyles.styles.map((style) => (
97 |
103 | ));
104 |
105 | return {
106 | ...initialProps,
107 | emotionStyleTags,
108 | };
109 | };
110 |
--------------------------------------------------------------------------------
/components/home/AboutSection.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '@mui/material/styles';
2 | import {
3 | Container,
4 | Divider,
5 | Typography,
6 | Grid,
7 | Box,
8 | Button,
9 | List,
10 | ListItem,
11 | ListItemIcon,
12 | ListItemText,
13 | } from '@mui/material/';
14 | import { Code, Description } from '@mui/icons-material';
15 |
16 | import skillIcons from './constants/skillIcons';
17 |
18 | const CustomDivider = styled(Divider)(({ theme }) => ({
19 | height: '4px',
20 | width: '60px',
21 | backgroundColor: theme.palette.primary.main,
22 | }));
23 |
24 | interface AboutData {
25 | aboutTitle: string;
26 | aboutItems: string[];
27 | resumeTitle: string;
28 | resumeParagraph: string;
29 | resumeButton: string;
30 | resumeLink: string;
31 | skillsTitle: string;
32 | }
33 |
34 | export default function About({ aboutData: t }: { aboutData: AboutData }) {
35 | return (
36 | theme.palette.grey[900] }}
40 | >
41 |
42 |
43 |
44 |
45 | {t.aboutTitle}
46 |
47 |
48 |
49 |
50 |
51 |
52 | {t.aboutItems.map((item) => (
53 |
54 |
55 |
56 |
57 |
58 |
59 | ))}
60 |
61 |
62 |
63 |
64 |
65 | {t.resumeTitle}
66 |
67 |
68 |
69 |
70 |
71 |
72 | {t.resumeParagraph}
73 |
74 |
75 | }
78 | href={t.resumeLink}
79 | rel="noopener"
80 | size="large"
81 | sx={{ m: 2 }}
82 | target="_blank"
83 | variant="outlined"
84 | >
85 | {t.resumeButton}
86 |
87 |
88 |
89 |
90 |
91 | {t.skillsTitle}
92 |
93 |
94 |
95 |
96 |
97 |
108 | {skillIcons.map((skillIcon) => (
109 |
121 | {skillIcon.icon}
122 |
123 | ))}
124 |
125 |
126 |
127 |
128 |
129 | );
130 | }
131 |
--------------------------------------------------------------------------------
/locales/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeo": {
3 | "title": "Hrithika R | Front-end developer",
4 | "description": "Hrithika R is a front-end developer, experienced in creating beautiful and functional websites and web applications.",
5 | "url": "",
6 | "previewImage": ""
7 | },
8 | "heroData": {
9 | "greetings": "HELLO, ",
10 | "introduction": "I'm Hrithika R",
11 | "role": "Front-end Developer",
12 | "paragraph": "Passionate about coding, and making my client ideas come true.",
13 | "button1": "Check my work",
14 | "button2": "Contact me"
15 | },
16 | "aboutData": {
17 | "aboutTitle": "About me",
18 | "aboutItems": [
19 | "I'm a front-end developer based in Spring, Texas.",
20 | "I like to design and build beautiful and functional websites and web applications, taking special care to produce clear and understandable code.",
21 | "I love teamwork and good communication, always open to feedback and willing to learn new things.",
22 | "In my free time I like to play the guitar, read a good book or play videogames."
23 | ],
24 | "resumeTitle": "Resumé",
25 | "resumeParagraph": "You can check my resumé in the link below.",
26 | "resumeButton": "Resumé",
27 | "resumeLink": "/resume/resume-en.pdf",
28 | "skillsTitle": "Skills"
29 | },
30 | "portfolioData": {
31 | "portfolioTitle": "Portfolio",
32 | "projects": [
33 | {
34 | "name": "Loaner",
35 | "projectUrl": "https://github.com/Hash-Debug",
36 | "repoUrl": "https://github.com/Hash-Debug",
37 | "imgPath": "projectMedia/loaner.png",
38 | "imgAlt": "Loaner",
39 | "summary": "Loan Application",
40 | "keyFeatures": [
41 | "User-friendly",
42 | "Realtime Database",
43 | "Brand customization",
44 | "Paid subscriptions"
45 | ],
46 | "technologies": ["React", "Firebase", "Material-UI"]
47 | },
48 | {
49 | "name": "Loaner",
50 | "projectUrl": "https://github.com/Hash-Debug",
51 | "repoUrl": "https://github.com/Hash-Debug",
52 | "imgPath": "projectMedia/loaner.png",
53 | "imgAlt": "Loaner",
54 | "summary": "Loan Application",
55 | "keyFeatures": [
56 | "User-friendly",
57 | "Realtime Database",
58 | "Brand customization",
59 | "Paid subscriptions"
60 | ],
61 | "technologies": ["React", "Firebase", "Material-UI"]
62 | },
63 | {
64 | "name": "Loaner",
65 | "projectUrl": "https://github.com/Hash-Debug",
66 | "repoUrl": "https://github.com/Hash-Debug",
67 | "imgPath": "projectMedia/loaner.png",
68 | "imgAlt": "Loaner",
69 | "summary": "Loan Application",
70 | "keyFeatures": [
71 | "User-friendly",
72 | "Realtime Database",
73 | "Brand customization",
74 | "Paid subscriptions"
75 | ],
76 | "technologies": ["React", "Firebase", "Material-UI"]
77 | },
78 | {
79 | "name": "Loaner",
80 | "projectUrl": "https://github.com/Hash-Debug",
81 | "repoUrl": "https://github.com/Hash-Debug",
82 | "imgPath": "projectMedia/loaner.png",
83 | "imgAlt": "Loaner",
84 | "summary": "Loan Application",
85 | "keyFeatures": [
86 | "User-friendly",
87 | "Realtime Database",
88 | "Brand customization",
89 | "Paid subscriptions"
90 | ],
91 | "technologies": ["React", "Firebase", "Material-UI"]
92 | }
93 |
94 | ]
95 | },
96 |
97 | "contactData": {
98 | "title": "Contact",
99 | "p1": "If you are interested in hiring me for your project please use the form below to get in touch. Want to know how I work and what I can offer? Check out my ",
100 | "p2": "portfolio",
101 | "p3": " and ",
102 | "p4": "resumé",
103 | "resumeLink": "/resume/resume-en.pdf",
104 | "subtitle": "You can also find me on the following channels",
105 | "formTitle": "Get in touch"
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/components/home/ContactSection.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/legacy/image';
2 | import { useState } from 'react';
3 | import { useFormik, FormikHelpers } from 'formik';
4 | import * as Yup from 'yup';
5 | import {
6 | Box,
7 | Container,
8 | Grid,
9 | TextField,
10 | Typography,
11 | Divider,
12 | Button,
13 | Card,
14 | CardContent,
15 | IconButton,
16 | Avatar,
17 | } from '@mui/material';
18 |
19 | import ShortCenteredDivider from '../ui/ShortCenteredDivider';
20 | import Link from '../Link';
21 | import profilePicture from '../../public/profile-picture.jpeg';
22 |
23 | import socialIcons from './constants/socialIcons';
24 |
25 | interface ContactData {
26 | title: string;
27 | p1: string;
28 | p2: string;
29 | p3: string;
30 | p4: string;
31 | resumeLink: string;
32 | subtitle: string;
33 | formTitle: string;
34 | firstNameLabel: string;
35 | lastNameLabel: string;
36 | messageLabel: string;
37 | placeholder: string;
38 | submitButton: string;
39 | requiredErrorMessage: string;
40 | }
41 |
42 | interface ContactFormFields {
43 | firstName: string;
44 | lastName: string;
45 | email: string;
46 | message: string;
47 | }
48 |
49 | export default function Contact({
50 | contactData: t,
51 | }: {
52 | contactData: ContactData;
53 | }) {
54 | const [displayMessage, setDisplayMessage] = useState(false);
55 | const [senderFirstName, setSenderFirstName] = useState('');
56 |
57 | const initialValues: ContactFormFields = {
58 | firstName: '',
59 | lastName: '',
60 | email: '',
61 | message: '',
62 | };
63 |
64 | const validationSchema = Yup.object({
65 | firstName: Yup.string().required(t.requiredErrorMessage),
66 | lastName: Yup.string().required(t.requiredErrorMessage),
67 | email: Yup.string()
68 | .email("Invalid")
69 | .required(t.requiredErrorMessage),
70 | });
71 |
72 | const onSubmit = async (
73 | values: ContactFormFields,
74 | onSubmitProps: FormikHelpers
75 | ) => {
76 | await fetch('/api/mail', {
77 | method: 'POST',
78 | headers: { 'Content-Type': 'application/json' },
79 | body: JSON.stringify({
80 | firstName: values.firstName,
81 | lastName: values.lastName,
82 | email: values.email,
83 | message: values.message,
84 | }),
85 | });
86 |
87 | setSenderFirstName(values.firstName);
88 | onSubmitProps.resetForm();
89 | setDisplayMessage(true);
90 | };
91 |
92 | const formik = useFormik({
93 | initialValues,
94 | onSubmit,
95 | validationSchema,
96 | });
97 |
98 | const { errors, touched, values, handleChange, handleSubmit, getFieldProps } =
99 | formik;
100 |
101 | return (
102 |
103 |
104 |
105 | {t.title}
106 |
107 |
108 |
109 |
110 |
111 |
112 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | {t.p1}
126 | {t.p2}
127 | {t.p3}
128 |
129 | {t.p4}
130 |
131 | .
132 |
133 |
134 |
135 |
136 |
137 |
138 |
145 | {t.subtitle}
146 |
147 |
148 |
149 | {socialIcons.map((socialIcon) => (
150 | theme.palette.common.white,
159 | '&:hover': {
160 | fill: (theme) => theme.palette.primary.main,
161 | },
162 | '&:focus': {
163 | fill: (theme) => theme.palette.primary.main,
164 | },
165 | }}
166 | target="_blank"
167 | >
168 | {socialIcon.icon}
169 |
170 | ))}
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 | );
180 | }
181 |
--------------------------------------------------------------------------------
/components/home/PortfolioSection.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/legacy/image';
2 | import {
3 | Box,
4 | Container,
5 | Typography,
6 | Grid,
7 | Card,
8 | CardContent,
9 | Chip,
10 | List,
11 | ListItem,
12 | ListItemIcon,
13 | ListItemText,
14 | ButtonGroup,
15 | Button,
16 | } from '@mui/material/';
17 | import { ChevronRight, GitHub, Visibility } from '@mui/icons-material';
18 |
19 | import Link from '../Link';
20 | import ShortCenteredDivider from '../ui/ShortCenteredDivider';
21 | import getDataUrlWithShimmerEffect from '../../utils/getDataUrlWithShimmerEffect';
22 |
23 | interface Project {
24 | name: string;
25 | projectUrl: string;
26 | repoUrl: string;
27 | imgPath: string;
28 | imgAlt: string;
29 | summary: string;
30 | keyFeatures: string[];
31 | technologies: string[];
32 | }
33 |
34 | interface PortfolioData {
35 | portfolioTitle: string;
36 | projects: Project[];
37 | }
38 |
39 | export default function Portfolio({
40 | portfolioData: t,
41 | }: {
42 | portfolioData: PortfolioData;
43 | }) {
44 | return (
45 |
46 |
47 |
48 | {t.portfolioTitle}
49 |
50 |
51 |
52 |
53 |
54 | {t.projects.map((project) => (
55 |
56 |
67 |
77 |
78 |
87 |
88 |
89 | {/* Overlay */}
90 |
106 |
107 | }
113 | target="_blank"
114 | >
115 | Repo
116 |
117 | }
123 | target="_blank"
124 | >
125 | Live
126 |
127 |
128 |
129 |
130 |
131 |
142 |
143 |
152 | {project.name}
153 |
154 |
159 | {project.summary}
160 |
161 |
162 | *': {
168 | flex: {
169 | xs: '0 0 100%',
170 | lg: '0 0 50%',
171 | },
172 | },
173 | }}
174 | >
175 | {project.keyFeatures.map((feature) => (
176 |
177 |
178 |
179 |
180 |
181 |
182 | ))}
183 |
184 |
185 |
186 |
187 | {project.technologies.map((e) => (
188 |
195 | ))}
196 |
197 |
198 |
199 |
200 | ))}
201 |
202 |
203 |
204 | );
205 | }
206 |
--------------------------------------------------------------------------------
/public/hero-graphic.svg:
--------------------------------------------------------------------------------
1 |
43 |
--------------------------------------------------------------------------------