diff --git a/package.json b/package.json index 94f7310..a73c27e 100644 --- a/package.json +++ b/package.json @@ -49,12 +49,14 @@ "react-dom": "^18.2.0", "react-headroom": "^3.2.1", "react-icons": "^4.10.1", + "react-syntax-highlighter": "^15.5.0", "react-tippy": "^1.4.0", "sanity": "^3.0.0", "sanity-plugin-internationalized-array": "^1.7.0", "sanity-plugin-media": "^2.2.1", "styled-components": "^5.2.0", - "tailwind-merge": "^1.12.0" + "tailwind-merge": "^1.12.0", + "typed.js": "^2.0.16" }, "devDependencies": { "@commitlint/cli": "^16.3.0", @@ -72,6 +74,7 @@ "@testing-library/react": "^13.4.0", "@types/react": "^18.2.7", "@types/react-headroom": "^3.2.0", + "@types/react-syntax-highlighter": "^15.5.7", "@typescript-eslint/eslint-plugin": "^5.59.7", "@typescript-eslint/parser": "^5.59.7", "autoprefixer": "^10.4.14", diff --git a/sanity/deskStructure.js b/sanity/deskStructure.js index a8564fc..951e43c 100644 --- a/sanity/deskStructure.js +++ b/sanity/deskStructure.js @@ -6,7 +6,7 @@ const freeTimeSection = [ 'tvSeries', 'videoGame', ]; -const sharedSection = ['shortText', 'skillIcon']; +const sharedSection = ['codeSnippet', 'shortText', 'skillIcon']; export const customStructure = (S) => S.list() @@ -39,4 +39,12 @@ export const customStructure = (S) => sharedSection.map((section) => S.documentTypeListItem(section)) ) ), + S.listItem() + .title('Single Pages') + .child( + S.editor() + .id('homeContent') + .schemaType('homeContent') + .documentId('homeContent') + ), ]); diff --git a/sanity/schema.ts b/sanity/schema.ts index f0258dd..6560e2f 100644 --- a/sanity/schema.ts +++ b/sanity/schema.ts @@ -1,5 +1,7 @@ import { type SchemaTypeDefinition } from 'sanity'; +import codeSnippet from '@/schemas/codeSnippet'; +import homeContent from '@/schemas/homeContent'; import randomFact from '@/schemas/randomFact'; import book from '../src/schemas/book'; @@ -17,6 +19,8 @@ import videoGame from '../src/schemas/videoGame'; export const schema: { types: SchemaTypeDefinition[] } = { types: [ book, + codeSnippet, + homeContent, job, language, podcast, diff --git a/src/app/(public)/page.tsx b/src/app/(public)/page.tsx index 0ea98da..600771f 100644 --- a/src/app/(public)/page.tsx +++ b/src/app/(public)/page.tsx @@ -1,15 +1,39 @@ import * as React from 'react'; +import Intro from '@/components/organisms/home/Intro'; +import Summary from '@/components/organisms/home/Summary'; import Seo from '@/components/Seo'; +import { codeSnippetsQuery } from '@/queries/codeSnippets'; + +import { sanityClient } from '../../../sanity/lib/client'; + +import { CodeSnippet } from '@/types/CodeSnippet'; + +const getData = async () => { + // const homeData: HomeContent[] = await sanityClient.fetch(homeContentQuery); + const codeSnippets: CodeSnippet[] = await sanityClient.fetch( + codeSnippetsQuery + ); + + return { + codeSnippets, + // homeContent: homeData[0], + }; +}; + const HomePage = async () => { + const { codeSnippets } = await getData(); + return (
-
- Homepage :) +
+ + +
diff --git a/src/components/organisms/home/Code.tsx b/src/components/organisms/home/Code.tsx new file mode 100644 index 0000000..747ce54 --- /dev/null +++ b/src/components/organisms/home/Code.tsx @@ -0,0 +1,81 @@ +'use client'; + +import { useTheme } from 'next-themes'; +import React, { useEffect, useState } from 'react'; +import SyntaxHighlighter from 'react-syntax-highlighter'; +import { + atomOneLight, + darcula, +} from 'react-syntax-highlighter/dist/cjs/styles/hljs'; +import Typed, { TypedOptions } from 'typed.js'; + +import { CodeSnippet } from '@/types/CodeSnippet'; + +export interface CodeSnippetsProps { + codeSnippets: CodeSnippet[]; +} + +const Code = ({ codeSnippets }: CodeSnippetsProps) => { + const [loading, setLoading] = useState(true); + + const { theme } = useTheme(); + + const ideStyle = theme === 'dark' ? darcula : atomOneLight; + + useEffect(() => { + setTimeout(() => { + setLoading(false); + }, 2000); + }, []); + + useEffect(() => { + if (!loading) { + const typedStringsElement = document.getElementById('typed-strings'); + const typedElement = document.getElementById('typed'); + + if (typedStringsElement && typedElement) { + const typedOptions: TypedOptions = { + stringsElement: '#typed-strings', + typeSpeed: 40, + backSpeed: 10, + backDelay: 5000, + loop: true, + loopCount: 0, + smartBackspace: true, + showCursor: true, + cursorChar: '_', + }; + + const typed = new Typed(typedElement, typedOptions); + + // Clean up the Typed.js instance when the component unmounts + return () => { + typed.destroy(); + }; + } + } + }, [loading]); + + return ( +
+
+ {loading ? _ : null} + {!loading && + codeSnippets.map((snippet) => ( + + {snippet.code} + + ))} +
+ +
+ ); +}; + +export default Code; diff --git a/src/components/organisms/home/Intro.tsx b/src/components/organisms/home/Intro.tsx new file mode 100644 index 0000000..1368ef2 --- /dev/null +++ b/src/components/organisms/home/Intro.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +import { shuffleArray } from '@/lib/helper'; + +import Code, { CodeSnippetsProps } from '@/components/organisms/home/Code'; +import Photo from '@/components/organisms/home/Photo'; + +const Intro = ({ codeSnippets }: CodeSnippetsProps) => { + return ( +
+

Hey there, I'm Marta šŸ‘‹šŸ»

+ +
+ + + +
+
+ ); +}; + +export default Intro; diff --git a/src/components/organisms/home/Photo.tsx b/src/components/organisms/home/Photo.tsx new file mode 100644 index 0000000..87c16e0 --- /dev/null +++ b/src/components/organisms/home/Photo.tsx @@ -0,0 +1,35 @@ +import Image from 'next/image'; +import React from 'react'; + +const Photo = () => { + return ( +
+
+
+
Software Engineer
+
Lifelong learner
+
Remote work enthusiast
+
+
+ Avatar + Avatar +
+
+
+ ); +}; + +export default Photo; diff --git a/src/components/organisms/home/Summary.tsx b/src/components/organisms/home/Summary.tsx new file mode 100644 index 0000000..da005b5 --- /dev/null +++ b/src/components/organisms/home/Summary.tsx @@ -0,0 +1,74 @@ +import Image from 'next/image'; +import React from 'react'; + +const Summary = () => { + return ( + <> +
+ I'm a software engineer based in Turin, Italy, and I am currently + working at + + Resourcify + + . +
+ +

+ I hold a MSc in Advanced Computer Science from the University of + Manchester, and have four years of experience at + + BJSS + + and + + Booking.com + + . +

+ +

+ My skill set embraces a range of programming languages, including Java, + Kotlin, Python, C# and TypeScript, as well as frontend frameworks such + as React and Angular. +

+ +

+ While I have a solid foundation in backend development, my heart truly + lies in the exciting realm of full-stack engineering. +

+ +

+ In my free time, Iā€™m a fiction writer, an avid bookworm, an oboist and + alto singer, and a travel photographer. +

+ + ); +}; + +export default Summary; diff --git a/src/pages/api/code-snippets.ts b/src/pages/api/code-snippets.ts new file mode 100644 index 0000000..33e2a0d --- /dev/null +++ b/src/pages/api/code-snippets.ts @@ -0,0 +1,19 @@ +import { NextApiRequest, NextApiResponse } from 'next'; + +import { codeSnippetsQuery } from '@/queries/codeSnippets'; + +import { sanityClient } from '../../../sanity/lib/client'; + +import { CodeSnippet } from '@/types/CodeSnippet'; + +const codeSnippetsApi = async (req: NextApiRequest, res: NextApiResponse) => { + const codeSnippets: CodeSnippet[] = await sanityClient.fetch( + codeSnippetsQuery + ); + + res.status(200).json({ + codeSnippets, + }); +}; + +export default codeSnippetsApi; diff --git a/src/pages/api/home.ts b/src/pages/api/home.ts new file mode 100644 index 0000000..d4ec10c --- /dev/null +++ b/src/pages/api/home.ts @@ -0,0 +1,17 @@ +import { NextApiRequest, NextApiResponse } from 'next'; + +import { homeContentQuery } from '@/queries/homeContent'; + +import { sanityClient } from '../../../sanity/lib/client'; + +import { HomeContent } from '@/types/HomeContent'; + +const homeApi = async (req: NextApiRequest, res: NextApiResponse) => { + const homeContent: HomeContent = await sanityClient.fetch(homeContentQuery); + + res.status(200).json({ + home: homeContent, + }); +}; + +export default homeApi; diff --git a/src/queries/codeSnippets.ts b/src/queries/codeSnippets.ts new file mode 100644 index 0000000..0f6169e --- /dev/null +++ b/src/queries/codeSnippets.ts @@ -0,0 +1,9 @@ +import { groq } from 'next-sanity'; + +export const codeSnippetsQuery = groq` +*[_type == "codeSnippet"] { + _id, + title, + "code": code.code, + "language": code.language, +}`; diff --git a/src/queries/homeContent.ts b/src/queries/homeContent.ts new file mode 100644 index 0000000..d61bb1b --- /dev/null +++ b/src/queries/homeContent.ts @@ -0,0 +1,13 @@ +import { groq } from 'next-sanity'; + +export const homeContentQuery = groq` +*[_type == "homeContent"] { + _id, + title, + threeLineSummary, + summary0, + summary1, + summary2, + summary3, + summary4, +}`; diff --git a/src/schemas/codeSnippet.ts b/src/schemas/codeSnippet.ts new file mode 100644 index 0000000..de59344 --- /dev/null +++ b/src/schemas/codeSnippet.ts @@ -0,0 +1,19 @@ +import { defineField, defineType } from 'sanity'; + +export default defineType({ + name: 'codeSnippet', + title: 'Code Snippet', + type: 'document', + fields: [ + defineField({ + name: 'title', + title: 'Title', + type: 'string', + }), + defineField({ + name: 'code', + title: 'Code Snippet', + type: 'code', + }), + ], +}); diff --git a/src/schemas/homeContent.ts b/src/schemas/homeContent.ts new file mode 100644 index 0000000..ce941da --- /dev/null +++ b/src/schemas/homeContent.ts @@ -0,0 +1,50 @@ +import { defineField, defineType } from 'sanity'; + +export default defineType({ + name: 'homeContent', + title: 'Home Content', + type: 'document', + fields: [ + defineField({ + name: 'title', + title: 'Title', + type: 'string', + }), + defineField({ + name: 'threeLineSummary', + title: '3-line Summary', + type: 'array', + of: [{ type: 'block' }], + }), + defineField({ + name: 'summary0', + title: 'Summary - 0', + type: 'text', + rows: 3, + }), + defineField({ + name: 'summary1', + title: 'Summary - 1', + type: 'text', + rows: 3, + }), + defineField({ + name: 'summary2', + title: 'Summary - 2', + type: 'text', + rows: 4, + }), + defineField({ + name: 'summary3', + title: 'Summary - 3', + type: 'text', + rows: 5, + }), + defineField({ + name: 'summary4', + title: 'Summary - 4', + type: 'array', + of: [{ type: 'block' }], + }), + ], +}); diff --git a/src/styles/globals.css b/src/styles/globals.css index 08b6f58..ebc25b7 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -260,4 +260,14 @@ .quiz-radio-button { color: white; } + + .cursor-blink { + animation: blinker 1s steps(2) infinite; + } + + @keyframes blinker { + 0% { + opacity: 0; + } + } } diff --git a/src/types/CodeSnippet.ts b/src/types/CodeSnippet.ts new file mode 100644 index 0000000..ad69154 --- /dev/null +++ b/src/types/CodeSnippet.ts @@ -0,0 +1,6 @@ +export interface CodeSnippet { + _id: string; + title: string; + code: string; + language: string; +} diff --git a/src/types/HomeContent.ts b/src/types/HomeContent.ts new file mode 100644 index 0000000..5bb1b98 --- /dev/null +++ b/src/types/HomeContent.ts @@ -0,0 +1,11 @@ +import { TypedObject } from '@portabletext/types'; + +export interface HomeContent { + title: string; + threeLineSummary: TypedObject; + summary0: string; + summary1: string; + summary2: string; + summary3: string; + summary4: TypedObject; +} diff --git a/yarn.lock b/yarn.lock index 63cba74..4766010 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1248,7 +1248,7 @@ resolved "https://verdaccio.mein-recycling.de/@babel%2fregjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.22.6", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.22.6", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.22.6" resolved "https://verdaccio.mein-recycling.de/@babel%2fruntime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438" integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ== @@ -4840,6 +4840,13 @@ hoist-non-react-statics "^3.3.0" redux "^4.0.0" +"@types/react-syntax-highlighter@^15.5.7": + version "15.5.7" + resolved "https://verdaccio.mein-recycling.de/@types%2freact-syntax-highlighter/-/react-syntax-highlighter-15.5.7.tgz#bd29020ccb118543d88779848f99059b64b02d0f" + integrity sha512-bo5fEO5toQeyCp0zVHBeggclqf5SQ/Z5blfFmjwO5dkMVGPgmiwZsJh9nu/Bo5L7IHTuGWrja6LxJVE2uB5ZrQ== + dependencies: + "@types/react" "*" + "@types/react-transition-group@^4.4.0", "@types/react-transition-group@^4.4.6": version "4.4.6" resolved "https://verdaccio.mein-recycling.de/@types%2freact-transition-group/-/react-transition-group-4.4.6.tgz#18187bcda5281f8e10dfc48f0943e2fdf4f75e2e" @@ -8323,6 +8330,13 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fault@^1.0.0: + version "1.0.4" + resolved "https://verdaccio.mein-recycling.de/fault/-/fault-1.0.4.tgz#eafcfc0a6d214fc94601e170df29954a4f842f13" + integrity sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA== + dependencies: + format "^0.2.0" + fb-watchman@^2.0.0: version "2.0.2" resolved "https://verdaccio.mein-recycling.de/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" @@ -8637,6 +8651,11 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +format@^0.2.0: + version "0.2.2" + resolved "https://verdaccio.mein-recycling.de/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" + integrity sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs= + forwarded@0.2.0: version "0.2.0" resolved "https://verdaccio.mein-recycling.de/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -9206,6 +9225,11 @@ header-case@^2.0.4: capital-case "^1.0.4" tslib "^2.0.3" +highlight.js@^10.4.1, highlight.js@~10.7.0: + version "10.7.3" + resolved "https://verdaccio.mein-recycling.de/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== + history@^5.3.0: version "5.3.0" resolved "https://verdaccio.mein-recycling.de/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b" @@ -11016,6 +11040,14 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" +lowlight@^1.17.0: + version "1.20.0" + resolved "https://verdaccio.mein-recycling.de/lowlight/-/lowlight-1.20.0.tgz#ddb197d33462ad0d93bf19d17b6c301aa3941888" + integrity sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw== + dependencies: + fault "^1.0.0" + highlight.js "~10.7.0" + lru-cache@10.0.0, "lru-cache@^9.1.1 || ^10.0.0": version "10.0.0" resolved "https://verdaccio.mein-recycling.de/lru-cache/-/lru-cache-10.0.0.tgz#b9e2a6a72a129d81ab317202d93c7691df727e61" @@ -12468,6 +12500,11 @@ pretty-ms@^7.0.1: dependencies: parse-ms "^2.1.0" +prismjs@^1.27.0: + version "1.29.0" + resolved "https://verdaccio.mein-recycling.de/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12" + integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q== + prismjs@~1.27.0: version "1.27.0" resolved "https://verdaccio.mein-recycling.de/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" @@ -12893,6 +12930,17 @@ react-style-proptype@^3.2.2: dependencies: prop-types "^15.5.4" +react-syntax-highlighter@^15.5.0: + version "15.5.0" + resolved "https://verdaccio.mein-recycling.de/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz#4b3eccc2325fa2ec8eff1e2d6c18fa4a9e07ab20" + integrity sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg== + dependencies: + "@babel/runtime" "^7.3.1" + highlight.js "^10.4.1" + lowlight "^1.17.0" + prismjs "^1.27.0" + refractor "^3.6.0" + react-tippy@^1.4.0: version "1.4.0" resolved "https://verdaccio.mein-recycling.de/react-tippy/-/react-tippy-1.4.0.tgz#e8a8b4085ec985e5c94fe128918b733b588a1465" @@ -12910,6 +12958,14 @@ react-transition-group@^4.3.0, react-transition-group@^4.4.5: loose-envify "^1.4.0" prop-types "^15.6.2" +react-typed@^1.2.0: + version "1.2.0" + resolved "https://verdaccio.mein-recycling.de/react-typed/-/react-typed-1.2.0.tgz#c5fb0bea4c972545c29fceb9c1d22495b6636b58" + integrity sha512-aDsaA6zkjAFJs8285APOqE85l/kwJ0/ZJmBhARwUrza4TTttrMM5FcMCGEDdThdfUdHo/0l9WXmxp1m2ik4qdw== + dependencies: + prop-types "^15.6.0" + typed.js "^2.0.6" + react-virtuoso@^4.3.11: version "4.3.11" resolved "https://verdaccio.mein-recycling.de/react-virtuoso/-/react-virtuoso-4.3.11.tgz#ab24e707287ef1b4bb5b52f3b14795ba896e9768" @@ -14820,6 +14876,11 @@ typed-array-length@^1.0.4: for-each "^0.3.3" is-typed-array "^1.1.9" +typed.js@^2.0.16, typed.js@^2.0.6: + version "2.0.16" + resolved "https://verdaccio.mein-recycling.de/typed.js/-/typed.js-2.0.16.tgz#2ba416821ec8a59521466f405784077eddb1d95e" + integrity sha512-IBB52GlJiTUOnomwdVVf7lWgC6gScn8md+26zTHj5oJWA+4pSuclHE76rbGI2hnyO+NT+QXdIUHbfjAY5nEtcw== + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://verdaccio.mein-recycling.de/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"