├── .eslintrc.json
├── .gitignore
├── LICENSE.md
├── README.md
├── app
├── Footer.tsx
├── Header.tsx
├── HomePage.tsx
├── globals.css
├── head.tsx
├── layout.tsx
└── page.tsx
├── components
├── About.tsx
├── CallToAction.tsx
├── Contact.tsx
├── Hero.tsx
├── SectionWrapper.tsx
├── Socials.tsx
├── experiences
│ ├── ExperienceCard.tsx
│ └── Experiences.tsx
├── projects
│ ├── ProjectCard.tsx
│ └── Projects.tsx
└── skills
│ ├── SkillCard.tsx
│ └── Skills.tsx
├── data.json
├── firebase.ts
├── next.config.js
├── package-lock.json
├── package.json
├── pages
└── api
│ └── mail.ts
├── postcss.config.js
├── public
├── contact.png
├── favicon.ico
├── herobg.jpg
├── herobgc.jpg
├── nextjs.svg
├── portfolio-fork-dark.png
├── portfolio-fork.png
├── vercel.svg
├── waving-hand.gif
└── waving-hand.png
├── tailwind.config.js
├── tsconfig.json
└── types
└── main.ts
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.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 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
38 | /.vscode/
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Jigar Sable
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |
26 |
27 | ## Introduction 👋
28 |
29 | Next Portfolio is built using Next.js and Tailwind CSS for a modern design and rapid development. TypeScript is utilized for code clarity and safety. Additionally, Firebase is being integrated for backend services such as realtime-database. The result is a dynamic and functional portfolio website that showcases the developer's skills and experience.
30 |
31 | ## Tech Stack 🛠️
32 |
33 | - [Next.js](https://nextjs.org)
34 | - [TypeScript](https://www.typescriptlang.org)
35 | - [Tailwind CSS](https://tailwindcss.com)
36 | - [Firebase](https://firebase.google.com)
37 | - [SendGrid](https://sendgrid.com)
38 | - [Framer Motion](https://www.framer.com/motion)
39 |
40 |
59 |
60 | ## Development 💻
61 |
62 | Here are the steps to run the portfolio locally.
63 |
64 | 1. Fork [this](https://github.com/jigar-sable/next-portfolio) repository.
65 |
66 | 2. Clone your forked copy of the repo
67 |
68 | ```bash
69 | git clone https://github.com//next-portfolio.git
70 | ```
71 |
72 | 3. Install dependencies
73 |
74 | ```bash
75 | npm i
76 | ```
77 |
78 | 4. Create a Firebase project and select the web app
79 |
80 | 5. Create an `.env.local` file in the root directory, and add the following variables with your firebase config:
81 | ```
82 | SENDGRID_API_KEY=XXXXXXXX
83 | NEXT_PUBLIC_FIREBASE_DATABASE_URL=XXXXXXXXXX
84 | MAIL_FROM=YOUR_MAIL_ID
85 | MAIL_TO=YOUR_MAIL_ID
86 | ```
87 |
88 |
89 | > **Note**: `SENDGRID_API_KEY` - Create an API key from "Settings" -> "API Keys" with "Restricted Access" to only "Mail Send"
90 |
91 | 1. Update the sample [data.json](https://github.com/jigar-sable/next-portfolio/blob/main/data.json) provided, with your data or directly import the same and edit using firebase later. (For storing images you can use [Cloudinary](https://cloudinary.com) or [Firebase Storage](https://firebase.google.com/docs/storage))
92 |
93 | 2. Import json data
94 |
95 | - Go to [Firebase Console](https://console.firebase.google.com) and select your project
96 | - Go to "Database" -> "Realtime Database" -> "Import JSON" and import the [data.json](https://github.com/jigar-sable/next-portfolio/blob/main/data.json) file
97 |
98 | 3. Run the project
99 |
100 | ```bash
101 | npm run dev
102 | ```
103 |
104 | ## Deployment 🚀
105 |
106 | 1. Create a Vercel account and select "Import Project"
107 |
108 | 2. Select the forked repository and deploy
109 |
110 | 3. Add the following environment variables in the Vercel dashboard:
111 | ```
112 | SENDGRID_API_KEY=XXXXXXXX
113 | NEXT_PUBLIC_FIREBASE_DATABASE_URL=XXXXXXXXXX
114 | MAIL_FROM=YOUR_MAIL_ID
115 | MAIL_TO=YOUR_MAIL_ID
116 | ```
117 | 4. Hurray! You successfully deployed the portfolio🥳
118 |
119 | ## License 📄
120 |
121 | This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/jigar-sable/next-portfolio/blob/main/LICENSE.md)
122 |
123 |
136 |
--------------------------------------------------------------------------------
/app/Footer.tsx:
--------------------------------------------------------------------------------
1 | import { social } from "@/types/main";
2 | import { useTheme } from "next-themes";
3 | import Image from "next/image";
4 | import Link from "next/link";
5 | import React from "react";
6 | import * as Fa from 'react-icons/fa';
7 |
8 | export default function Footer({ socials, name }: { socials: social[], name: string }) {
9 |
10 | const { theme } = useTheme()
11 |
12 | return (
13 |
47 | )
48 | }
--------------------------------------------------------------------------------
/app/Header.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { useState, useEffect } from 'react'
3 | import { useTheme } from 'next-themes'
4 | import Link from 'next/link'
5 | import { Link as ScrollLink } from 'react-scroll'
6 | import { FiSun, FiMoon } from 'react-icons/fi'
7 | import { FaNodeJs } from 'react-icons/fa'
8 | import { CgClose, CgMenuRight } from 'react-icons/cg'
9 |
10 | export default function Header({ logo }: { logo: string }) {
11 |
12 | const [navCollapse, setNavCollapse] = useState(true)
13 | const [scroll, setScroll] = useState(false)
14 | const { theme, setTheme } = useTheme()
15 |
16 | useEffect(() => {
17 | const updateScroll = () => {
18 | window.scrollY >= 90 ? setScroll(true) : setScroll(false)
19 | }
20 | window.addEventListener('scroll', updateScroll)
21 | }, [])
22 |
23 |
24 | const navs = ['home', 'about', 'projects', 'experience', 'contact']
25 |
26 | return (
27 |
102 | )
103 | }
--------------------------------------------------------------------------------
/app/HomePage.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { data } from "@/types/main";
3 | import Hero from "@/components/Hero";
4 | import About from "@/components/About";
5 | import Skills from "@/components/skills/Skills";
6 | import Projects from "@/components/projects/Projects";
7 | import Socials from "@/components/Socials";
8 | import Experiences from "@/components/experiences/Experiences";
9 | import Contact from "@/components/Contact";
10 | import CallToAction from "@/components/CallToAction";
11 | import Header from "./Header";
12 | import Footer from "./Footer";
13 |
14 | interface Props {
15 | data: data,
16 | }
17 |
18 | const HomePage = ({ data }: Props) => {
19 | return (
20 | <>
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | >
32 | )
33 | }
34 |
35 | export default HomePage
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | /* @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@100;200;300;400;500;600;700;800;900&display=swap"); */
2 |
3 | @tailwind base;
4 | @tailwind components;
5 | @tailwind utilities;
6 |
7 | * {
8 | box-sizing: border-box;
9 | padding: 0;
10 | margin: 0;
11 | }
12 |
13 | html {
14 | scroll-behavior: smooth !important;
15 | }
16 |
17 | /* body {
18 | font-family: "Poppins", sans-serif;
19 | } */
20 |
21 | ::-webkit-scrollbar {
22 | width: 0.5rem;
23 | }
24 |
25 | ::-webkit-scrollbar-thumb {
26 | background-color: #6565656a;
27 | }
28 |
29 | .scroll-hide::-webkit-scrollbar {
30 | display: none;
31 | }
--------------------------------------------------------------------------------
/app/head.tsx:
--------------------------------------------------------------------------------
1 | export default function Head() {
2 | return (
3 | <>
4 |
5 |
6 |
7 |
8 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | Portfolio | Jigar Sable - Full Stack Developer
22 | >
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import './globals.css'
3 | import { Poppins } from '@next/font/google'
4 | import { ThemeProvider } from 'next-themes'
5 | import { Analytics } from '@vercel/analytics/react';
6 |
7 | const poppins = Poppins({
8 | subsets: ['latin'],
9 | weight: ['300', '400', '500', '600', '700', '800'],
10 | variable: '--font-poppins'
11 | })
12 |
13 | export default function RootLayout({
14 | children,
15 | }: {
16 | children: React.ReactNode
17 | }) {
18 | return (
19 |
20 |
21 |
22 |
23 | {/* */}
24 | {children}
25 |
26 |
27 |
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { FaNodeJs } from "react-icons/fa"
2 | import HomePage from "./HomePage"
3 | // import { ref, get } from "firebase/database"
4 | // import { database } from "@/firebase"
5 |
6 | async function getData() {
7 |
8 | // return await (await get(ref(database))).val()
9 |
10 | const DB_URL = process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL + '/.json'
11 | const res = await fetch(DB_URL, { cache: 'no-store' })
12 | const data = res.json()
13 | return data
14 | }
15 |
16 | export default async function page() {
17 |
18 | const data = await getData()
19 |
20 | return (
21 | <>
22 | {data ?
23 |
24 | :
25 |
26 |
27 |
Loading...
28 |
29 | }
30 | >
31 | )
32 | }
--------------------------------------------------------------------------------
/components/About.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image"
2 | import { about } from "@/types/main";
3 | import Link from "next/link";
4 | import SectionWrapper from "./SectionWrapper";
5 | import { BiLinkExternal } from "react-icons/bi";
6 |
7 | interface Props {
8 | aboutData: about,
9 | name: string
10 | }
11 |
12 | const About = ({ aboutData, name }: Props) => {
13 |
14 | const { aboutImage, aboutImageCaption, title, about, resumeUrl, callUrl } = aboutData
15 |
16 | return (
17 |
18 | About Me
19 |
20 |
21 |
22 |
23 | {aboutImageCaption || '< I Build Stuff 🚀 />'}
24 |
25 |
26 |
27 |
28 |
{name}
29 |
{title}
30 |
{about}
31 |
32 | {resumeUrl.trim() && Resume}
33 | {callUrl.trim() && Book a 1:1 call }
34 |
35 |
36 |
37 |
38 |
39 | )
40 | }
41 |
42 | export default About
--------------------------------------------------------------------------------
/components/CallToAction.tsx:
--------------------------------------------------------------------------------
1 | import { useTheme } from "next-themes"
2 | import Image from "next/image"
3 | import Link from "next/link"
4 | import { BiLinkExternal } from "react-icons/bi"
5 | import { FaGithub } from "react-icons/fa"
6 | import SectionWrapper from "./SectionWrapper"
7 |
8 | const CallToAction = () => {
9 |
10 | const { theme } = useTheme();
11 |
12 | return (
13 |
14 |
15 |
Loved this portfolio?
16 |
Make this yours by forking.
17 |
Fork this template on GitHub start building your own portfolio website.
18 |
19 |
20 |
21 | Fork Now
22 |
23 |
24 | Visit Docs
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | {/* */}
33 |
34 | )
35 | }
36 |
37 | export default CallToAction
--------------------------------------------------------------------------------
/components/Contact.tsx:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { useState } from "react";
3 | import { BiLoaderAlt } from "react-icons/bi";
4 | import SectionWrapper from "./SectionWrapper"
5 | import Image from "next/image";
6 | import { ToastContainer, toast } from 'react-toastify';
7 | import 'react-toastify/dist/ReactToastify.min.css';
8 |
9 | const Contact = () => {
10 |
11 | const [values, setValues] = useState({
12 | name: "",
13 | email: "",
14 | message: "",
15 | });
16 | const [loading, setLoading] = useState(false);
17 | const [success, setSuccess] = useState(false);
18 |
19 | const handleSubmit = async (e: React.FormEvent) => {
20 | e.preventDefault();
21 |
22 | if (!values.name.trim() || !values.email.trim() || !values.message.trim()) {
23 | toast.warning("Empty Fields!")
24 | return false;
25 | }
26 |
27 | setLoading(true);
28 | axios.post("/api/mail", {
29 | name: values.name,
30 | email: values.email,
31 | message: values.message,
32 | }).then((res) => {
33 | if (res.status === 200) {
34 | setValues({ name: "", email: "", message: "" });
35 | setLoading(false);
36 | setSuccess(true);
37 | toast.success(res.data.message)
38 | } else {
39 | setLoading(false);
40 | toast.error(res.data.message)
41 | }
42 | }).catch((err) => {
43 | setLoading(false);
44 | toast.error(err.message)
45 | });
46 | };
47 |
48 | const handleChange = (e: | React.ChangeEvent | React.ChangeEvent) => {
49 | setValues((prevInput) => ({
50 | ...prevInput,
51 | [e.target.name]: e.target.value,
52 | }));
53 | };
54 |
55 | return (
56 |
57 | Contact Me
58 |
59 |
60 |
61 | {/* blurDataURL="https://i.imgur.com/owZdhjA.png" */}
62 |
63 |
64 |
Get in touch
65 |
My inbox is always open! 💌 Whether you've got a burning question or want to drop a friendly "hello", I'm all ears!👂 Let's chat! 🎉
66 |
67 |
75 |
76 |
77 |
78 | )
79 | }
80 |
81 | export default Contact
82 |
--------------------------------------------------------------------------------
/components/Hero.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image'
2 | import { useTheme } from 'next-themes'
3 | import { Link as ScrollLink } from 'react-scroll'
4 | import Typewriter from 'typewriter-effect';
5 | import { IoIosArrowForward } from 'react-icons/io';
6 | import wavingHand from '@/public/waving-hand.gif';
7 | import { main } from '@/types/main';
8 |
9 | interface HeroProps {
10 | mainData: main
11 | }
12 |
13 | const Hero = ({ mainData }: HeroProps) => {
14 |
15 | const { theme } = useTheme()
16 | const { name, titles, heroImage, shortDesc, techStackImages } = mainData
17 |
18 | return (
19 |
20 |
21 |
22 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | Hey
38 |
39 |
40 |
41 | I'm {name}
42 |
43 |
44 |
45 | I am into
46 |
47 |
58 |
59 |
60 |
61 | {shortDesc}
62 |
63 |
64 | {/*
65 | SPPU Prep
66 | */}
67 |
68 |
76 | About Me
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | )
106 | }
107 |
108 | export default Hero
--------------------------------------------------------------------------------
/components/SectionWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode } from 'react';
2 | import { motion } from 'framer-motion';
3 | import { useInView } from 'react-intersection-observer';
4 |
5 | const sectionVariants = {
6 | hidden: { opacity: 0 },
7 | visible: { opacity: 1, transition: { duration: 0.6, ease: 'easeInOut' } }
8 | };
9 |
10 | const SectionWrapper = ({ children, id, className }: { children: ReactNode, id: string, className: string }) => {
11 |
12 | const [ref, inView] = useInView({
13 | threshold: 0.2,
14 | triggerOnce: true
15 | });
16 |
17 | return (
18 |
26 | {children}
27 |
28 | )
29 | }
30 |
31 | export default SectionWrapper
--------------------------------------------------------------------------------
/components/Socials.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Link from 'next/link';
3 | import * as Fa from 'react-icons/fa';
4 | import { social } from '@/types/main';
5 | // import { IconType } from '../node_modules/react-icons/lib'
6 |
7 | const Socials = ({ socials }: { socials: social[] }) => {
8 |
9 | // const components = Object.values(Fa);
10 | // console.log(components[0])
11 |
12 | return (
13 |
14 | {socials.map((s: social) => {
15 | // const { icon } = s;
16 | // const iconIndex = components.indexOf(icon : IconType)
17 | // const Icon = components[`Fa${icon}`]
18 | return (
19 |
20 | {
21 | //@ts-ignore
22 | React.createElement(Fa[`${s.icon}`])
23 | }
24 | {/* */}
25 |
26 | )
27 | })}
28 |
29 | )
30 | }
31 |
32 | export default Socials
--------------------------------------------------------------------------------
/components/experiences/ExperienceCard.tsx:
--------------------------------------------------------------------------------
1 | import { MdSchool, MdWork } from 'react-icons/md'
2 | import { motion } from 'framer-motion';
3 | import { useInView } from 'react-intersection-observer';
4 |
5 | interface ExperienceProps {
6 | index: number,
7 | company: string,
8 | position: string,
9 | desc: string[],
10 | institute: string,
11 | degree: string,
12 | duration: string,
13 | }
14 |
15 | const Experience = ({ index, company, position, desc, institute, degree, duration }: ExperienceProps) => {
16 |
17 | const [ref, inView] = useInView({
18 | threshold: 0.5,
19 | triggerOnce: true
20 | });
21 |
22 | const cardVariants = {
23 | hidden: { x: index % 2 === 0 ? 20 : -20, opacity: 0 },
24 | visible: { x: 0, opacity: 1, transition: { duration: 0.6, ease: 'easeInOut' } }
25 | };
26 |
27 | return (
28 |
29 |
30 |
31 |
32 | {company && }
33 | {institute && }
34 |
35 |
36 |
42 | {company || institute}
43 | {position || degree} | {duration}
44 |
45 | {desc && desc.map((d, i) => (
46 | - {d}
47 | ))}
48 |
49 |
50 |
51 | )
52 | }
53 |
54 | export default Experience
--------------------------------------------------------------------------------
/components/experiences/Experiences.tsx:
--------------------------------------------------------------------------------
1 | import { education, experience } from "@/types/main"
2 | import { useState } from "react"
3 | import { ViewAll } from "../projects/Projects"
4 | import SectionWrapper from "../SectionWrapper"
5 | import ExperienceCard from "./ExperienceCard"
6 |
7 | interface Props {
8 | experienceData: experience[]
9 | educationData: education[]
10 | }
11 |
12 | const Experiences = ({ experienceData, educationData }: Props) => {
13 |
14 | const [show, setShow] = useState("Experience")
15 | const [viewAll, setViewAll] = useState(false)
16 |
17 | const [experiences, setExperiences] = useState([...experienceData].reverse() as experience[])
18 | const [educations, setEducations] = useState([...educationData].reverse() as education[])
19 |
20 | return (
21 |
22 | Experience
23 |
24 |
25 | {['Experience', 'Education'].map((e, i) => (
26 |
27 | ))
28 | }
29 |
30 |
31 |
32 |
33 |
34 |
35 | {viewAll ?
36 | (show === "Experience" ? experiences : educations).map((e, i) => (
37 | // @ts-ignore
38 |
39 | ))
40 | :
41 | (show === "Experience" ? experiences : educations).slice(0, 2).map((e, i) => (
42 | // @ts-ignore
43 |
44 | ))
45 | }
46 |
47 |
48 |
49 |
50 | {(show === "Experience" ? experiences : educations).length > 2 &&
51 | setViewAll(!viewAll)} />
52 | }
53 |
54 |
55 | )
56 | }
57 |
58 | export default Experiences
--------------------------------------------------------------------------------
/components/projects/ProjectCard.tsx:
--------------------------------------------------------------------------------
1 | import { project } from "@/types/main"
2 | import Image from "next/image"
3 | import Link from "next/link"
4 | import { FaGithub, FaVideo } from "react-icons/fa"
5 | import { BiLinkExternal } from "react-icons/bi"
6 | import { motion } from 'framer-motion';
7 | import { useInView } from 'react-intersection-observer';
8 |
9 | const cardVariants = {
10 | hidden: { y: 50, opacity: 0 },
11 | visible: { y: 0, opacity: 1, transition: { duration: 0.6, ease: 'easeInOut' } }
12 | };
13 |
14 | const Project = ({ name, image, category, techstack, links }: project) => {
15 |
16 | const [ref, inView] = useInView({
17 | threshold: 0.2,
18 | triggerOnce: true
19 | });
20 |
21 | return (
22 |
28 |
29 |
30 |
31 | {(links.visit.trim() || links.code.trim() || links.video.trim()) &&
32 |
33 | {links.visit.trim() &&
34 |
35 |
36 |
37 | }
38 | {links.code.trim() &&
39 |
40 |
41 |
42 | }
43 | {links.video.trim() &&
44 |
45 |
46 |
47 | }
48 |
49 | }
50 |
51 |
52 |
53 |
{name}
54 |
Tech Stack: {techstack}
55 |
56 |
57 |
58 | )
59 | }
60 |
61 | export default Project
--------------------------------------------------------------------------------
/components/projects/Projects.tsx:
--------------------------------------------------------------------------------
1 | import { project } from "@/types/main";
2 | import { useEffect, useState } from "react";
3 | import { Link } from "react-scroll";
4 | import SectionWrapper from "../SectionWrapper";
5 | import ProjectCard from "./ProjectCard";
6 |
7 | interface Props {
8 | projectsData: project[]
9 | }
10 |
11 | const Projects = ({ projectsData }: Props) => {
12 |
13 | const [projects, setProjects] = useState([...projectsData].reverse() as project[])
14 |
15 | // const categories = ['All', ...Array.from(new Set(projects.map((s) => s.category)))]
16 | const categories = [...Array.from(new Set(projects.map((s) => s.category)))]
17 |
18 | // const [category, setCategory] = useState(categories[0] || "All")
19 | const [category, setCategory] = useState(categories[0])
20 |
21 | const [filteredProjects, setFilteredProjects] = useState(projects as project[])
22 | const [viewAll, setViewAll] = useState(false)
23 |
24 | const filterProjects = (cat: string) => {
25 | setViewAll(false)
26 | setCategory(cat)
27 | // cat === "All" ? setFilteredProjects(projects) :
28 | setFilteredProjects(projects.filter((p: project) => p.category.toLowerCase() === cat.toLowerCase()));
29 | }
30 |
31 | useEffect(() => {
32 | filterProjects(categories.includes('MERN Stack') ? "MERN Stack" : categories[0])
33 | // eslint-disable-next-line react-hooks/exhaustive-deps
34 | }, [])
35 |
36 | return (
37 |
38 | Projects
39 |
40 |
41 | {categories.map((c: string = "", i: number) => (
42 | filterProjects(c)} className={`p-1.5 md:p-2 w-full text-sm md:text-base text-center capitalize rounded-md ${category.toLowerCase() === c.toLowerCase() ? "bg-violet-600 text-white" : "hover:bg-gray-100 hover:dark:bg-grey-900"} cursor-pointer transition-all`}>
43 | {c}
44 |
45 | ))}
46 |
47 |
48 |
49 | {filteredProjects.slice(0, viewAll ? filteredProjects.length : 6).map((p: project, i: number) => (
50 |
51 | ))}
52 |
53 |
54 |
55 | {filteredProjects.length > 6
56 | &&
57 | setViewAll(!viewAll)} />
58 | }
59 |
60 | )
61 | }
62 |
63 | export default Projects
64 |
65 | type MouseEventHandler = (event: React.MouseEvent) => void;
66 |
67 | export const ViewAll = ({ handleClick, title, scrollTo }: { handleClick: MouseEventHandler, title: string, scrollTo: string }) => {
68 | return (
69 | <>
70 |
71 |
72 | {title === 'View All' ?
73 |
76 | :
77 | handleClick()}
85 | >{title}
86 | }
87 |
88 | >
89 | )
90 | }
--------------------------------------------------------------------------------
/components/skills/SkillCard.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image"
2 | import { useEffect, useState } from 'react'
3 | import { FastAverageColor } from 'fast-average-color';
4 | import { skill } from "@/types/main";
5 | import { useTheme } from "next-themes";
6 |
7 | const Skill = ({ name, image }: skill) => {
8 |
9 | const { theme } = useTheme();
10 | const [bgColor, setBgColor] = useState("")
11 | useEffect(() => {
12 | new FastAverageColor().getColorAsync(image)
13 | .then(color => {
14 | const rgba = color.rgb.split(')')
15 | setBgColor(rgba[0] + ',0.07)')
16 | })
17 | .catch(e => {
18 | console.log(e);
19 | })
20 | }, [image])
21 |
22 | return (
23 |
24 |
26 |
27 |
28 |
{name}
29 |
30 | )
31 | }
32 |
33 | export default Skill
--------------------------------------------------------------------------------
/components/skills/Skills.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { skill } from '@/types/main';
3 | import SkillCard from "./SkillCard"
4 | import SectionWrapper from '../SectionWrapper';
5 |
6 | interface Props {
7 | skillData: skill[]
8 | }
9 |
10 | const Skills = ({ skillData }: Props) => {
11 |
12 | const categories = Array.from(new Set(skillData.map((s: { category: any; }) => s.category)))
13 | const [category, setCategory] = useState(categories[0])
14 |
15 | return (
16 |
17 | Tech Stack
18 |
19 |
20 | {categories.map((c: string, i: number) => (
21 | setCategory(c)} className={`p-1.5 md:p-2 text-sm md:text-base w-full text-center cursor-pointer rounded-md ${category.toLowerCase() === c.toLowerCase() ? "bg-violet-600 dark:bg-violet-600 text-white" : "bg-white dark:bg-grey-800 hover:bg-gray-100 hover:dark:bg-grey-900"} transition-all capitalize`}>{c}
22 | ))}
23 |
24 |
25 |
26 | {skillData.filter((s: skill) => s.category.toLowerCase() === category.toLowerCase()).map((s: any, i: number) => (
27 |
28 | ))}
29 |
30 |
31 |
32 | )
33 | }
34 |
35 | export default Skills
--------------------------------------------------------------------------------
/data.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": {
3 | "name": "Jhon Doe",
4 | "shortDesc": "I focus on developing user-friendly web applications that meet the client's requirements, with attention to detail, scalability, and performance.",
5 | "titles": [
6 | "Frontend Development",
7 | "Backend Development",
8 | "App Development"
9 | ],
10 | "heroImage": "https://img.freepik.com/free-psd/3d-illustration-person-with-sunglasses_23-2149436188.jpg",
11 | "techStackImages": [
12 | "https://img.icons8.com/color/144/000000/material-ui.png",
13 | "https://img.icons8.com/color/144/000000/html-5--v1.png",
14 | "https://img.icons8.com/color/144/000000/redux.png",
15 | "https://img.icons8.com/color/144/000000/chakra-ui.png"
16 | ]
17 | },
18 | "about": {
19 | "aboutImage": "https://img.freepik.com/free-psd/3d-illustration-person-with-sunglasses_23-2149436188.jpg",
20 | "aboutImageCaption": "< I build stuff 🚀/>",
21 | "title": "Full Stack Developer",
22 | "about": "I am a Full-Stack developer based in Pune, India. I'm an Information Technology undergraduate from SPPU, and my journey in web development began during my first year of college. I'm passionate about creating beautiful, functional, and user-friendly websites and applications, and I'm constantly pushing myself to learn and grow as a developer. Love building full-stack clones and side projects.",
23 | "callUrl": "",
24 | "resumeUrl": ""
25 | },
26 | "socials": [
27 | {
28 | "icon": "FaLinkedin",
29 | "link": "https://www.linkedin.com/in/jhon-doe"
30 | },
31 | {
32 | "icon": "FaGithub",
33 | "link": "https://github.com/jhon-doe"
34 | },
35 | {
36 | "icon": "FaInstagram",
37 | "link": "https://www.instagram.com/jhondoe"
38 | },
39 | {
40 | "icon": "FaTwitter",
41 | "link": "https://twitter.com/jhondoe"
42 | }
43 | ],
44 | "skills": [
45 | {
46 | "name": "JavaScript",
47 | "image": "https://img.icons8.com/color/144/null/javascript--v1.png",
48 | "category": "Frontend"
49 | },
50 | {
51 | "name": "Firebase",
52 | "image": "https://img.icons8.com/color/144/null/firebase.png",
53 | "category": "Backend"
54 | },
55 | {
56 | "name": "GitHub",
57 | "image": "https://img.icons8.com/material-outlined/96/null/github.png",
58 | "category": "Tools"
59 | }
60 | ],
61 | "projects": [
62 | {
63 | "name": "Project Title",
64 | "techstack": "HTML5, CSS3, JavaScript, jQuery",
65 | "category": "MERN Stack",
66 | "image": "https://media.geeksforgeeks.org/wp-content/cdn-uploads/20210114225740/10-Best-Web-Development-Project-Ideas-For-Beginners-in-2021.png",
67 | "links": {
68 | "code": "",
69 | "video": "",
70 | "visit": ""
71 | }
72 | },
73 | {
74 | "name": "Project Title",
75 | "techstack": "Flutter, Dart, Firebase",
76 | "category": "Flutter",
77 | "image": "https://media.geeksforgeeks.org/wp-content/cdn-uploads/20210114225740/10-Best-Web-Development-Project-Ideas-For-Beginners-in-2021.png",
78 | "links": {
79 | "code": "",
80 | "video": "",
81 | "visit": ""
82 | }
83 | },
84 | {
85 | "name": "Project Title",
86 | "techstack": "PHP, MySQL, HTML5, CSS3, Bootstrap, JavaScript",
87 | "category": "LAMP Stack",
88 | "image": "https://media.geeksforgeeks.org/wp-content/cdn-uploads/20210114225740/10-Best-Web-Development-Project-Ideas-For-Beginners-in-2021.png",
89 | "links": {
90 | "code": "",
91 | "video": "",
92 | "visit": ""
93 | }
94 | }
95 | ],
96 | "educations": [
97 | {
98 | "institute": "XYZ School | CBSE",
99 | "degree": "HSC Science",
100 | "duration": "2014 - 2016"
101 | },
102 | {
103 | "institute": "XYZ College Of Engineering",
104 | "degree": "B.Tech CSE",
105 | "duration": "2016 - 2020",
106 | "desc": [
107 | "Pursuing IT Engineering course from ABC University",
108 | "Worked on web application development using HTML, CSS, and JavaScript, designing a database system using SQL",
109 | "Familiar with software development methodologies and project management practices, including Agile and Waterfall methodologies."
110 | ]
111 | }
112 | ],
113 | "experiences": [
114 | {
115 | "company": "ABC Tech Solutions",
116 | "position": "Web Developer",
117 | "duration": "2020 -2022",
118 | "desc": [
119 | "Assisted with design and development of project.",
120 | "Worked on building the product from scratch and built multiple dashboards.",
121 | "Skills: HTML · CSS · JavaScript · TailwindCSS · ReactJS · NodeJS · MySQL · REST APIs"
122 | ]
123 | },
124 | {
125 | "company": "XYZ Tech Solutions",
126 | "position": "Web Developer",
127 | "duration": "2022 - present",
128 | "desc": [
129 | "Assisted with design and development of project.",
130 | "Worked on building the product from scratch and built multiple dashboards.",
131 | "Skills: HTML · CSS · JavaScript · TailwindCSS · ReactJS · NodeJS · MySQL · REST APIs"
132 | ]
133 | }
134 | ]
135 | }
136 |
--------------------------------------------------------------------------------
/firebase.ts:
--------------------------------------------------------------------------------
1 | import { getApp, getApps, initializeApp } from "firebase/app";
2 | import { getDatabase } from 'firebase/database';
3 |
4 | const firebaseConfig = {
5 | apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
6 | authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
7 | databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL,
8 | projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECTID,
9 | storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
10 | messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDERID,
11 | appId: process.env.NEXT_PUBLIC_FIREBASE_APPID,
12 | measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENTD
13 | };
14 |
15 | // Initialize Firebase
16 | const app = getApps().length ? getApp() : initializeApp(firebaseConfig);
17 | export const database = getDatabase(app);
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | experimental: {
4 | appDir: true,
5 | },
6 | images: {
7 | domains: ['res.cloudinary.com', 'firebasestorage.googleapis.com', 'img.icons8.com', 'raw.githubusercontent.com', 'i.imgur.com', 'img.freepik.com','media.geeksforgeeks.org']
8 | }
9 | }
10 |
11 | module.exports = nextConfig
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-portfolio",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@next/font": "13.1.6",
13 | "@sendgrid/mail": "^7.7.0",
14 | "@types/node": "18.13.0",
15 | "@types/react": "18.0.28",
16 | "@types/react-dom": "18.0.10",
17 | "@vercel/analytics": "^0.1.11",
18 | "axios": "^1.3.3",
19 | "eslint": "8.34.0",
20 | "eslint-config-next": "13.1.6",
21 | "fast-average-color": "^9.3.0",
22 | "firebase": "^9.17.1",
23 | "framer-motion": "^9.0.4",
24 | "next": "13.1.6",
25 | "next-themes": "^0.2.1",
26 | "react": "18.2.0",
27 | "react-dom": "18.2.0",
28 | "react-icons": "^4.7.1",
29 | "react-intersection-observer": "^9.4.2",
30 | "react-scroll": "^1.8.9",
31 | "react-toastify": "^9.1.1",
32 | "typescript": "4.9.5",
33 | "typewriter-effect": "^2.19.0"
34 | },
35 | "devDependencies": {
36 | "@types/react-scroll": "^1.8.6",
37 | "autoprefixer": "^10.4.13",
38 | "postcss": "^8.4.21",
39 | "tailwindcss": "^3.2.6"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/pages/api/mail.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from "next";
2 | import sgMail from "@sendgrid/mail";
3 |
4 | sgMail.setApiKey(process.env.SENDGRID_API_KEY || '');
5 |
6 | type Data = {
7 | message: string;
8 | };
9 |
10 | export default async function handler(
11 | req: NextApiRequest,
12 | res: NextApiResponse
13 | ) {
14 | if (req.method === "POST") {
15 | const {
16 | name,
17 | email,
18 | message,
19 | }: { name: string; email: string; message: string } = req.body;
20 | const msg = `Name: ${name}\r\n Email: ${email}\r\n Message: ${message}`;
21 | const data = {
22 | to: process.env.MAIL_TO as string,
23 | from: process.env.MAIL_FROM as string,
24 | subject: `${name.toUpperCase()} sent you a message from Portfolio`,
25 | text: `Email => ${email}`,
26 | html: msg.replace(/\r\n/g, "
"),
27 | };
28 | try {
29 | await sgMail.send(data);
30 | res.status(200).json({ message: "Your message was sent successfully." });
31 | } catch (err) {
32 | res.status(500).json({ message: `There was an error sending your message. ${err}` });
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/contact.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigar-sable/next-portfolio/62e9ecaded91dc1a87bcd6842dff0f6fdf134023/public/contact.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigar-sable/next-portfolio/62e9ecaded91dc1a87bcd6842dff0f6fdf134023/public/favicon.ico
--------------------------------------------------------------------------------
/public/herobg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigar-sable/next-portfolio/62e9ecaded91dc1a87bcd6842dff0f6fdf134023/public/herobg.jpg
--------------------------------------------------------------------------------
/public/herobgc.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigar-sable/next-portfolio/62e9ecaded91dc1a87bcd6842dff0f6fdf134023/public/herobgc.jpg
--------------------------------------------------------------------------------
/public/nextjs.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/portfolio-fork-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigar-sable/next-portfolio/62e9ecaded91dc1a87bcd6842dff0f6fdf134023/public/portfolio-fork-dark.png
--------------------------------------------------------------------------------
/public/portfolio-fork.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigar-sable/next-portfolio/62e9ecaded91dc1a87bcd6842dff0f6fdf134023/public/portfolio-fork.png
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/waving-hand.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigar-sable/next-portfolio/62e9ecaded91dc1a87bcd6842dff0f6fdf134023/public/waving-hand.gif
--------------------------------------------------------------------------------
/public/waving-hand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jigar-sable/next-portfolio/62e9ecaded91dc1a87bcd6842dff0f6fdf134023/public/waving-hand.png
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | darkMode: 'class',
4 | content: [
5 | "./app/**/*.{js,ts,jsx,tsx}",
6 | "./pages/**/*.{js,ts,jsx,tsx}",
7 | "./components/**/*.{js,ts,jsx,tsx}",
8 | ],
9 | theme: {
10 | extend: {
11 | // fontFamily: {
12 | // poppins: ['var(--font-poppins)']
13 | // },
14 | colors: {
15 | grey: {
16 | 800: '#18141c',
17 | 900: '#120f16'
18 | },
19 | yellow: {
20 | 400: '#FEDE00',
21 | }
22 | },
23 | backgroundImage: {
24 | heropattern: "url(/herobgc.jpg)",
25 | }
26 | },
27 | plugins: [],
28 | }
29 | }
--------------------------------------------------------------------------------
/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 | "incremental": true,
17 | "plugins": [
18 | {
19 | "name": "next"
20 | }
21 | ],
22 | "baseUrl": ".",
23 | "paths": {
24 | "@/*": ["./*"]
25 | }
26 | },
27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
28 | "exclude": ["node_modules"]
29 | }
30 |
--------------------------------------------------------------------------------
/types/main.ts:
--------------------------------------------------------------------------------
1 | type skill = {
2 | name: string,
3 | image: string,
4 | category: string
5 | }
6 |
7 | type project = {
8 | name: string,
9 | image: string,
10 | techstack: string,
11 | category: string,
12 | links: {
13 | visit: string,
14 | code: string,
15 | video: string
16 | }
17 | }
18 |
19 | type experience = {
20 | company: string,
21 | position: string,
22 | startDate: string,
23 | endDate: string,
24 | desc: string[]
25 | }
26 |
27 | type education = {
28 | institute: string,
29 | degree: string,
30 | startDate: string,
31 | endDate: string,
32 | }
33 |
34 | type main = {
35 | name: string,
36 | titles: string[],
37 | heroImage: string,
38 | shortDesc: string,
39 | techStackImages: string[],
40 | }
41 |
42 | type about = {
43 | aboutImage: string,
44 | aboutImageCaption: string,
45 | title: string,
46 | about: string,
47 | resumeUrl: string,
48 | callUrl: string
49 | }
50 |
51 | type social = {
52 | name: string,
53 | icon: string,
54 | link: string
55 | }
56 |
57 | type data = {
58 | main: main,
59 | about: about,
60 | skills: skill[],
61 | projects: project[],
62 | experiences: experience[],
63 | educations: education[]
64 | socials: social[]
65 | }
66 |
67 | export type { data, main, about, skill, project, experience, education, social };
--------------------------------------------------------------------------------