├── .env.example ├── .eslintrc.json ├── public ├── preview.png ├── vercel.svg └── next.svg ├── src ├── app │ ├── favicon.ico │ ├── globals.css │ ├── editor │ │ ├── components │ │ │ ├── EditorFormErrorMessage.tsx │ │ │ ├── EditorFormLabel.tsx │ │ │ ├── EditorFormInput.tsx │ │ │ ├── EditorSectionHeader.tsx │ │ │ └── EditorFormTextarea.tsx │ │ └── page.tsx │ ├── _components │ │ ├── footer │ │ │ └── index.tsx │ │ ├── devices │ │ │ ├── desktop │ │ │ │ └── index.tsx │ │ │ └── mobile │ │ │ │ └── index.tsx │ │ ├── modal │ │ │ └── index.tsx │ │ └── template │ │ │ └── index.tsx │ ├── api │ │ └── serialize │ │ │ └── route.ts │ ├── layout.tsx │ ├── you │ │ └── page.tsx │ └── page.tsx ├── helpers │ ├── types.ts │ ├── serializer.ts │ ├── constants.ts │ └── schema.ts ├── mocks │ └── index.ts └── assets │ ├── undraw_profile_re_4a55.svg │ └── undraw_product_iteration_kjok.svg ├── next.config.mjs ├── postcss.config.mjs ├── .gitignore ├── tsconfig.json ├── package.json ├── tailwind.config.ts ├── LICENSE └── README.md /.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_URL= -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /public/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manuelbento19/my-folio/HEAD/public/preview.png -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manuelbento19/my-folio/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | * { 6 | margin: 0; 7 | padding: 0; 8 | box-sizing: border-box; 9 | scroll-behavior: smooth; 10 | } 11 | -------------------------------------------------------------------------------- /src/app/editor/components/EditorFormErrorMessage.tsx: -------------------------------------------------------------------------------- 1 | export const EditorFormMessageError = ({ message }: { message?: string }) => { 2 | if (!message) return null; 3 | return {message}; 4 | }; 5 | -------------------------------------------------------------------------------- /src/helpers/types.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { PortfolioSchema } from "./schema"; 3 | 4 | export type Portfolio = z.infer; 5 | 6 | export enum Social { 7 | Github, 8 | Linkedin, 9 | Youtube, 10 | Medium, 11 | Twitter, 12 | Facebook, 13 | Instagram, 14 | Outro, 15 | } 16 | -------------------------------------------------------------------------------- /src/app/editor/components/EditorFormLabel.tsx: -------------------------------------------------------------------------------- 1 | export const EditorFormLabel: React.FC<{ 2 | htmlFor?: string; 3 | text: string; 4 | }> = ({ htmlFor, text }) => { 5 | return ( 6 | <> 7 | 10 | 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /src/helpers/serializer.ts: -------------------------------------------------------------------------------- 1 | export abstract class Serializer { 2 | static encode(data: String) { 3 | try { 4 | return Buffer.from(data).toString("base64"); 5 | } catch {} 6 | } 7 | static decode(data: string) { 8 | try { 9 | const result = Buffer.from(data, "base64").toString("utf-8"); 10 | return JSON.parse(result); 11 | } catch {} 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/editor/components/EditorFormInput.tsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from "react"; 2 | 3 | export const EditorFormInput = forwardRef< 4 | HTMLInputElement, 5 | React.InputHTMLAttributes 6 | >((props, ref) => ( 7 | <> 8 | 13 | 14 | )); 15 | 16 | EditorFormInput.displayName = "EditorFormInput"; 17 | -------------------------------------------------------------------------------- /src/app/editor/components/EditorSectionHeader.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | 3 | export const EditorSectionHeader: React.FC<{ 4 | title: string; 5 | children?: ReactNode; 6 | }> = ({ title, children }) => { 7 | return ( 8 |
9 |

10 | {title} 11 |

12 | 13 | {children} 14 |
15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /src/app/editor/components/EditorFormTextarea.tsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from "react"; 2 | 3 | export const EditorFormTextarea = forwardRef< 4 | HTMLTextAreaElement, 5 | React.TextareaHTMLAttributes 6 | >((props, ref) => { 7 | return ( 8 | <> 9 | 14 | 15 | ); 16 | }); 17 | 18 | EditorFormTextarea.displayName = "EditorFormTextarea"; 19 | -------------------------------------------------------------------------------- /.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 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env 30 | .env*.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /src/app/_components/footer/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from "react"; 2 | import { FaGithub } from "react-icons/fa"; 3 | 4 | const Footer = () => { 5 | return ( 6 | 15 | ); 16 | }; 17 | 18 | export default memo(Footer); 19 | -------------------------------------------------------------------------------- /src/app/api/serialize/route.ts: -------------------------------------------------------------------------------- 1 | import { Serializer } from "@/helpers/serializer"; 2 | import { Portfolio } from "@/helpers/types"; 3 | import { NextResponse } from "next/server"; 4 | 5 | export async function POST(request: Request) { 6 | try { 7 | const body: Portfolio = await request.json(); 8 | const base64 = Serializer.encode(JSON.stringify(body)); 9 | const appUrl = process.env.NEXT_PUBLIC_URL; 10 | const url = `${appUrl}/you?data=${base64}`; 11 | return NextResponse.json({ 12 | url, 13 | }); 14 | } catch (error) { 15 | return NextResponse.json(error); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/helpers/constants.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FaFacebook, 3 | FaGithub, 4 | FaInstagram, 5 | FaLink, 6 | FaLinkedin, 7 | FaMedium, 8 | FaTwitter, 9 | FaYoutube, 10 | } from "react-icons/fa"; 11 | import { Social } from "./types"; 12 | 13 | export const SOCIAIS = [ 14 | { type: Social.Github, icon: FaGithub }, 15 | { type: Social.Facebook, icon: FaFacebook }, 16 | { type: Social.Linkedin, icon: FaLinkedin }, 17 | { type: Social.Instagram, icon: FaInstagram }, 18 | { type: Social.Medium, icon: FaMedium }, 19 | { type: Social.Outro, icon: FaLink }, 20 | { type: Social.Twitter, icon: FaTwitter }, 21 | { type: Social.Youtube, icon: FaYoutube }, 22 | ]; 23 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { ChakraProvider } from "@chakra-ui/react"; 2 | import type { Metadata } from "next"; 3 | import { Inter } from "next/font/google"; 4 | import "./globals.css"; 5 | 6 | const inter = Inter({ subsets: ["latin"] }); 7 | 8 | export const metadata: Metadata = { 9 | title: "MyFolio", 10 | description: "Create your own portfolio without code!", 11 | }; 12 | 13 | export default function RootLayout({ 14 | children, 15 | }: Readonly<{ 16 | children: React.ReactNode; 17 | }>) { 18 | return ( 19 | 20 | 21 | {children} 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./src/*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /src/app/_components/devices/desktop/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const Desktop = ({ children }: React.PropsWithChildren) => { 4 | return ( 5 |
6 |
7 |
11 | {children} 12 |
13 |
17 |
18 |
19 |
20 |
21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/app/_components/devices/mobile/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const Mobile = ({ children }: React.PropsWithChildren) => { 4 | return ( 5 |
6 |
10 |
11 |
12 |
13 |
14 | {children} 15 |
16 |
17 |
18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-folio", 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 | "@chakra-ui/react": "^2.8.2", 13 | "@emotion/react": "^11.13.0", 14 | "@emotion/styled": "^11.13.0", 15 | "@hookform/resolvers": "^3.9.0", 16 | "framer-motion": "^11.3.24", 17 | "next": "14.2.5", 18 | "react": "^18", 19 | "react-dom": "^18", 20 | "react-hook-form": "^7.52.2", 21 | "react-icons": "^5.2.1", 22 | "zod": "^3.23.8" 23 | }, 24 | "devDependencies": { 25 | "@types/node": "^20", 26 | "@types/react": "^18", 27 | "@types/react-dom": "^18", 28 | "eslint": "^8", 29 | "eslint-config-next": "14.2.5", 30 | "postcss": "^8", 31 | "tailwindcss": "^3.4.1", 32 | "typescript": "^5" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | content: [ 5 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | keyframes: { 12 | typing: { 13 | "0%": { 14 | width: "0%", 15 | visibility: "hidden" 16 | }, 17 | "100%": { 18 | width: "100%" 19 | } 20 | }, 21 | blink: { 22 | "50%": { 23 | borderColor: "transparent" 24 | }, 25 | "100%": { 26 | borderColor: "white" 27 | } 28 | } 29 | }, 30 | animation: { 31 | typing: "typing 2s steps(20) infinite alternate, blink .7s infinite" 32 | } 33 | }, 34 | }, 35 | plugins: [], 36 | }; 37 | export default config; 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Manuel Bento 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 | -------------------------------------------------------------------------------- /src/app/you/page.tsx: -------------------------------------------------------------------------------- 1 | import { Serializer } from "@/helpers/serializer"; 2 | import { Portfolio } from "@/helpers/types"; 3 | import { Metadata } from "next"; 4 | import { notFound } from "next/navigation"; 5 | import { Template } from "../_components/template"; 6 | 7 | type Props = { 8 | searchParams: { 9 | data: string; 10 | }; 11 | }; 12 | 13 | export async function generateMetadata({ 14 | searchParams, 15 | }: Props): Promise { 16 | const portfolio: Portfolio = Serializer.decode( 17 | JSON.stringify(searchParams?.data) 18 | ); 19 | if (!portfolio) return {}; 20 | 21 | return { 22 | title: `${portfolio?.personal?.name} - MyFolio`, 23 | openGraph: { 24 | siteName: "MyFolio", 25 | type: "website", 26 | title: `${portfolio?.personal?.name} - MyFolio`, 27 | }, 28 | }; 29 | } 30 | 31 | export default function Page({ searchParams }: Props) { 32 | if (!searchParams?.data) return notFound(); 33 | 34 | const portfolio: Portfolio = Serializer.decode( 35 | JSON.stringify(searchParams.data) 36 | ); 37 | if (!portfolio) return notFound(); 38 | 39 | return ( 40 |
41 |