diff --git a/.env b/.env new file mode 100644 index 0000000..caf61de --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +OST_GITHUB_ID=Ov23li1QEGhmHL1zZy9s +OST_GITHUB_SECRET=3735cccb709cf25c9f3a18363e9a14ceec3e77e3 +OST_REPO_SLUG=owenstack.github.io \ No newline at end of file diff --git a/.github/workflows/astro.yml b/.github/workflows/astro.yml deleted file mode 100644 index 2ee0e27..0000000 --- a/.github/workflows/astro.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Deploy Astro site to Pages - -on: - push: - branches: ["master"] # adjust if needed - workflow_dispatch: - -permissions: - contents: read - pages: write - id-token: write - -jobs: - build: - name: Build - runs-on: ubuntu-latest - steps: - - name: Checkout your repository using git - uses: actions/checkout@v4 - - - name: Install, build, and upload - uses: withastro/action@v3 - with: - package-manager: bun@latest - - deploy: - name: Deploy to GitHub Pages - needs: build - runs-on: ubuntu-latest - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/nextjs.yml b/.github/workflows/nextjs.yml new file mode 100644 index 0000000..ed74736 --- /dev/null +++ b/.github/workflows/nextjs.yml @@ -0,0 +1,93 @@ +# Sample workflow for building and deploying a Next.js site to GitHub Pages +# +# To get started with Next.js see: https://nextjs.org/docs/getting-started +# +name: Deploy Next.js site to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Build job + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Detect package manager + id: detect-package-manager + run: | + if [ -f "${{ github.workspace }}/yarn.lock" ]; then + echo "manager=yarn" >> $GITHUB_OUTPUT + echo "command=install" >> $GITHUB_OUTPUT + echo "runner=yarn" >> $GITHUB_OUTPUT + exit 0 + elif [ -f "${{ github.workspace }}/package.json" ]; then + echo "manager=npm" >> $GITHUB_OUTPUT + echo "command=ci" >> $GITHUB_OUTPUT + echo "runner=npx --no-install" >> $GITHUB_OUTPUT + exit 0 + else + echo "Unable to determine package manager" + exit 1 + fi + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: ${{ steps.detect-package-manager.outputs.manager }} + - name: Setup Pages + uses: actions/configure-pages@v5 + with: + # Automatically inject basePath in your Next.js configuration file and disable + # server side image optimization (https://nextjs.org/docs/api-reference/next/image#unoptimized). + # + # You may remove this line if you want to manage the configuration yourself. + static_site_generator: next + - name: Restore cache + uses: actions/cache@v4 + with: + path: | + .next/cache + # Generate a new cache whenever packages or source files change. + key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }} + # If source files changed but packages didn't, rebuild from a prior cache. + restore-keys: | + ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}- + - name: Install dependencies + run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }} + - name: Build with Next.js + run: ${{ steps.detect-package-manager.outputs.runner }} next build + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ./out + + # Deployment job + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 16d54bb..b46ec10 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,37 @@ -# build output -dist/ -# generated types -.astro/ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies -node_modules/ +/node_modules +/.pnp +.pnp.js +.yarn/ -# logs +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug npm-debug.log* yarn-debug.log* yarn-error.log* -pnpm-debug.log* - -# environment variables +# local env files +.env*.local .env -.env.production -# macOS-specific files -.DS_Store +# vercel +.vercel -# jetbrains setting folder -.idea/ +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 26d70b4..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "recommendations": ["astro-build.astro-vscode", "unifiedjs.vscode-mdx"], - "unwantedRecommendations": [] -} diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 230708d..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "command": "./node_modules/.bin/astro dev", - "name": "Development server", - "request": "launch", - "type": "node-terminal" - } - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 4d360cb..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "typescript.tsdk": "node_modules/typescript/lib" -} diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/README.md b/README.md index 758716e..c403366 100644 --- a/README.md +++ b/README.md @@ -1,68 +1,36 @@ -# Astro Starter Kit: Blog +This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). -```sh -npm create astro@latest -- --template blog -``` - -[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/blog) -[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/blog) -[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/blog/devcontainer.json) - -> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! - -![blog](https://github.com/withastro/astro/assets/2244813/ff10799f-a816-4703-b967-c78997e8323d) +## Getting Started -Features: +First, run the development server: -- ✅ Minimal styling (make it your own!) -- ✅ 100/100 Lighthouse performance -- ✅ SEO-friendly with canonical URLs and OpenGraph data -- ✅ Sitemap support -- ✅ RSS Feed support -- ✅ Markdown & MDX support - -## 🚀 Project Structure - -Inside of your Astro project, you'll see the following folders and files: - -```text -├── public/ -├── src/ -│   ├── components/ -│   ├── content/ -│   ├── layouts/ -│   └── pages/ -├── astro.config.mjs -├── README.md -├── package.json -└── tsconfig.json +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev ``` -Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. - -There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. -The `src/content/` directory contains "collections" of related Markdown and MDX documents. Use `getCollection()` to retrieve posts from `src/content/blog/`, and type-check your frontmatter using an optional schema. See [Astro's Content Collections docs](https://docs.astro.build/en/guides/content-collections/) to learn more. +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. -Any static assets, like images, can be placed in the `public/` directory. +This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. -## 🧞 Commands +## Learn More -All commands are run from the root of the project, from a terminal: +To learn more about Next.js, take a look at the following resources: -| Command | Action | -| :------------------------ | :----------------------------------------------- | -| `npm install` | Installs dependencies | -| `npm run dev` | Starts local dev server at `localhost:4321` | -| `npm run build` | Build your production site to `./dist/` | -| `npm run preview` | Preview your build locally, before deploying | -| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | -| `npm run astro -- --help` | Get help using the Astro CLI | +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. -## 👀 Want to learn more? +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! -Check out [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). +## Deploy on Vercel -## Credit +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. -This theme is based off of the lovely [Bear Blog](https://github.com/HermanMartinus/bearblog/). +Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/app/(cms)/layout.tsx b/app/(cms)/layout.tsx new file mode 100644 index 0000000..30403d8 --- /dev/null +++ b/app/(cms)/layout.tsx @@ -0,0 +1,7 @@ +export default function CMSLayout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} diff --git a/app/(cms)/outstatic/[[...ost]]/page.tsx b/app/(cms)/outstatic/[[...ost]]/page.tsx new file mode 100644 index 0000000..d61ae7d --- /dev/null +++ b/app/(cms)/outstatic/[[...ost]]/page.tsx @@ -0,0 +1,8 @@ +import "outstatic/outstatic.css"; +import { Outstatic } from "outstatic"; +import { OstClient } from "outstatic/client"; + +export default async function Page({ params }: { params: { ost: string[] } }) { + const ostData = await Outstatic(); + return ; +} diff --git a/app/api/contact/route.ts b/app/api/contact/route.ts new file mode 100644 index 0000000..f43d6d7 --- /dev/null +++ b/app/api/contact/route.ts @@ -0,0 +1,47 @@ +import nodemailer from 'nodemailer'; + +export async function POST(req: Request) { + const body = await req.formData() + + const name = body.get('name'); + const email = body.get('email'); + const message = body.get('message'); + + if (!name || !email || !message) { + return new Response('Missing required form data', { status: 400 }); + } + + let transporter = nodemailer.createTransport({ + host: process.env.SMTP_HOST, + port: 587, + secure: false, + auth: { + user: process.env.SMTP_USER, + pass: process.env.SMTP_PASS + } + }) + + let mailOptions = { + from: 'owen@owenstack.github.io', + to: 'jenukohaefobi@gmail.com', + subject: 'Contact Form', + text: `New Form Submission: \n\nOne ${name} with email ${email} dropped a message whose contents are: \n\n${message}` + } + + try { + const info = await new Promise((resolve, reject) => { + transporter.sendMail(mailOptions, (error, info) => { + if (error) { + reject(error) + } else { + resolve(info) + } + }) + }) + console.log('Email sent: ', info.response) + return new Response('Email Sent Succesfully', {status: 200}) + } catch (error) { + console.error(error) + return new Response(`Error: ${error}`, {status: 500}) + } +} \ No newline at end of file diff --git a/app/api/outstatic/[[...ost]]/route.ts b/app/api/outstatic/[[...ost]]/route.ts new file mode 100644 index 0000000..21ae04b --- /dev/null +++ b/app/api/outstatic/[[...ost]]/route.ts @@ -0,0 +1,5 @@ +import { OutstaticApi } from "outstatic"; + +export const GET = OutstaticApi.GET; + +export const POST = OutstaticApi.POST; diff --git a/app/favicon.ico b/app/favicon.ico new file mode 100644 index 0000000..fb67702 Binary files /dev/null and b/app/favicon.ico differ diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..c4b183a --- /dev/null +++ b/app/globals.css @@ -0,0 +1,61 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 232 39% 99%; + --foreground: 232 54% 3%; + --muted: 52 6% 94%; + --muted-foreground: 52 10% 38%; + --popover: 232 39% 99%; + --popover-foreground: 232 54% 3%; + --card: 232 39% 99%; + --card-foreground: 232 54% 3%; + --border: 232 3% 93%; + --input: 232 3% 93%; + --primary: 232 79% 37%; + --primary-foreground: 232 79% 97%; + --secondary: 52 79% 37%; + --secondary-foreground: 0 0% 0%; + --accent: 52 79% 37%; + --accent-foreground: 0 0% 0%; + --destructive: 0 90% 36%; + --destructive-foreground: 0 90% 96%; + --ring: 232 79% 37%; + --radius: 0.5rem; + } + + .dark { + --background: 232 58% 1%; + --foreground: 232 26% 100%; + --muted: 52 6% 6%; + --muted-foreground: 52 10% 62%; + --popover: 232 58% 1%; + --popover-foreground: 232 26% 100%; + --card: 232 58% 1%; + --card-foreground: 232 26% 100%; + --border: 232 3% 14%; + --input: 232 3% 14%; + --primary: 232 79% 37%; + --primary-foreground: 232 79% 97%; + --secondary: 52 79% 37%; + --secondary-foreground: 0 0% 0%; + --accent: 52 79% 37%; + --accent-foreground: 0 0% 0%; + --destructive: 0 90% 51%; + --destructive-foreground: 0 0% 100%; + --ring: 232 79% 37%; + } +} + + + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..84e1240 --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,54 @@ +import type {Metadata} from "next"; +import {Analytics} from "@vercel/analytics/react"; +import {LanguageContextProvider, ThemeProvider} from "@/lib/providers"; +import {Toaster} from "@/components/ui/sonner"; +import Header from "@/components/Header"; +import {cn} from "@/lib/utils"; +import "./globals.css"; +import localFont from 'next/font/local' + +const body = localFont({src: '../assets/fonts/body.ttf'}) + +export const metadata: Metadata = { + title: { + template: "%s | Owenstack", + default: "Owenstack", + }, + description: + "Full-stack web developer that builds beautiful and functional websites", + metadataBase: new URL("https://owenstack.github.io"), + openGraph: { + images: "/og-image.jpg", + }, +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + + + +
+ {children} + + + + + + ); +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..5fe35e3 --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,17 @@ +import AboutSection from "@/components/AboutSection"; +import ContactSection from "@/components/ContactSection"; +import HeroSection from "@/components/HeroSection"; +import StackSection from "@/components/StackSection"; +import BlogSectionWrapper from "@/components/BlogSectionWrapper"; + +export default function Page() { + return ( +
+ + + + + +
+ ); +} diff --git a/app/posts/[slug]/page.tsx b/app/posts/[slug]/page.tsx new file mode 100644 index 0000000..a0f41e7 --- /dev/null +++ b/app/posts/[slug]/page.tsx @@ -0,0 +1,152 @@ +import { getDocumentBySlug } from "outstatic/server"; +import ReactMarkdown from "react-markdown"; +import Image from "next/image"; +import { Calendar, LoaderCircle } from "lucide-react"; +import { Suspense } from "react"; +import remarkGFM from "remark-gfm"; +import remarkEmoji from "remark-emoji"; +import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; +import { tomorrow } from "react-syntax-highlighter/dist/esm/styles/prism"; + +export default async function Page({ params }: { params: { slug: string } }) { + const post = await getData(params); + + return ( + + + + } + > + {post && ( +
+ {post.coverImage && ( + {post.title} + )} +

+ {post.title} +

+
+
+ {post.author?.picture && ( + {post.author?.name + )} + + {post.author?.name} + +
+
+ + + {new Date(post.publishedAt).toLocaleString()} + +
+
+
+ ( +

+ ), + h1: ({ node, ...props }) => ( +

+ ), + h2: ({ node, ...props }) => ( +

+ ), + h3: ({ node, ...props }) => ( +

+ ), + ul: ({ node, ...props }) => ( +
    + ), + ol: ({ node, ...props }) => ( +
      + ), + li: ({ node, ...props }) => ( +
    1. + ), + blockquote: ({ node, ...props }) => ( +
      + ), + code: ({ node, className, children, ...props }) => { + const match = /language-(\w+)/.exec(className || ""); + return match ? ( + + style={tomorrow as any} + language={match[1]} + PreTag="div" + > + {String(children).replace(/\n$/, "")} + + ) : ( + + {children} + + ); + }, + }} + > + {post.content} + +

+
+ )} +
+ ); +} + +async function getData(params: { slug: string }) { + const post = getDocumentBySlug("posts", params.slug, [ + "title", + "publishedAt", + "slug", + "author", + "content", + "coverImage", + ]); + + return post; +} diff --git a/app/posts/page.tsx b/app/posts/page.tsx new file mode 100644 index 0000000..9c25eca --- /dev/null +++ b/app/posts/page.tsx @@ -0,0 +1,63 @@ +import Link from "next/link"; +import { CalendarIcon, UserIcon } from "lucide-react"; +import { getDocuments } from "outstatic/server"; + +export default async function Page() { + const posts = await getData(); + return ( +
+

Latest Articles

+
+ {posts.map((article) => ( +
+ +
+ {article.title} +
+ + Read More + +
+
+
+

{article.title}

+

{article.description}

+
+
+ + {article.author?.name} +
+
+ + {new Date(article.publishedAt).toLocaleDateString()} +
+
+
+ +
+ ))} +
+
+ ); +} + +async function getData() { + const posts = getDocuments("posts", [ + "title", + "slug", + "coverImage", + "author", + "publishedAt", + "description", + ]); + return posts; +} diff --git a/assets/fonts/body.ttf b/assets/fonts/body.ttf new file mode 100644 index 0000000..797c4a5 Binary files /dev/null and b/assets/fonts/body.ttf differ diff --git a/assets/fonts/head.ttf b/assets/fonts/head.ttf new file mode 100644 index 0000000..19bb671 Binary files /dev/null and b/assets/fonts/head.ttf differ diff --git a/assets/fonts/logo.ttf b/assets/fonts/logo.ttf new file mode 100644 index 0000000..221f64c Binary files /dev/null and b/assets/fonts/logo.ttf differ diff --git a/assets/icons/bun.tsx b/assets/icons/bun.tsx new file mode 100644 index 0000000..d350553 --- /dev/null +++ b/assets/icons/bun.tsx @@ -0,0 +1,102 @@ +"use client"; + +import type {SVGProps} from "react"; +const Bun = (props: SVGProps) => ( + + + + + + + + + + + + + + + + + + + + +); +export default Bun; diff --git a/assets/icons/code.tsx b/assets/icons/code.tsx new file mode 100644 index 0000000..2141474 --- /dev/null +++ b/assets/icons/code.tsx @@ -0,0 +1,46 @@ +"use client"; + +import type {SVGProps} from "react"; +const VisualStudioCode = (props: SVGProps) => ( + + + + + + + + + + + + + + + + +); +export default VisualStudioCode; diff --git a/assets/icons/css.tsx b/assets/icons/css.tsx new file mode 100644 index 0000000..742f4c2 --- /dev/null +++ b/assets/icons/css.tsx @@ -0,0 +1,24 @@ +"use client"; + +import type {SVGProps} from "react"; +const CSS = (props: SVGProps) => ( + + + + + + +); +export default CSS; diff --git a/assets/icons/discord.tsx b/assets/icons/discord.tsx new file mode 100644 index 0000000..a352b43 --- /dev/null +++ b/assets/icons/discord.tsx @@ -0,0 +1,19 @@ +"use client"; + +import type {SVGProps} from "react"; +const Discord = (props: SVGProps) => ( + + + +); +export default Discord; diff --git a/assets/icons/docker.tsx b/assets/icons/docker.tsx new file mode 100644 index 0000000..446ba13 --- /dev/null +++ b/assets/icons/docker.tsx @@ -0,0 +1,16 @@ +"use client"; + +import type {SVGProps} from "react"; +const Docker = (props: SVGProps) => ( + + + +); +export default Docker; diff --git a/assets/icons/electron.tsx b/assets/icons/electron.tsx new file mode 100644 index 0000000..7213413 --- /dev/null +++ b/assets/icons/electron.tsx @@ -0,0 +1,51 @@ +"use client"; + +import type {SVGProps} from "react"; +const Electron = (props: SVGProps) => ( + + + + + + + + + +); +export default Electron; diff --git a/assets/icons/express.tsx b/assets/icons/express.tsx new file mode 100644 index 0000000..5d0d99d --- /dev/null +++ b/assets/icons/express.tsx @@ -0,0 +1,18 @@ +"use client"; + +import type {SVGProps} from "react"; +const Express = (props: SVGProps) => ( + + + +); +export default Express; diff --git a/assets/icons/figma.tsx b/assets/icons/figma.tsx new file mode 100644 index 0000000..e043a8c --- /dev/null +++ b/assets/icons/figma.tsx @@ -0,0 +1,42 @@ +"use client"; + +import type {SVGProps} from "react"; +const Figma = (props: SVGProps) => ( + + + + + + + + + + + + + + +); +export default Figma; diff --git a/assets/icons/git.tsx b/assets/icons/git.tsx new file mode 100644 index 0000000..011929d --- /dev/null +++ b/assets/icons/git.tsx @@ -0,0 +1,19 @@ +"use client"; + +import type {SVGProps} from "react"; +const Git = (props: SVGProps) => ( + + + +); +export default Git; diff --git a/assets/icons/github.tsx b/assets/icons/github.tsx new file mode 100644 index 0000000..d2ea631 --- /dev/null +++ b/assets/icons/github.tsx @@ -0,0 +1,17 @@ +"use client"; + +import type {SVGProps} from "react"; +const Github = (props: SVGProps) => ( + + + +); +export default Github; diff --git a/assets/icons/googledev.tsx b/assets/icons/googledev.tsx new file mode 100644 index 0000000..93ba49f --- /dev/null +++ b/assets/icons/googledev.tsx @@ -0,0 +1,36 @@ +"use client"; + +import type {SVGProps} from "react"; +const DeveloperStudentClub = (props: SVGProps) => ( + + + + + + + + + +); +export default DeveloperStudentClub; diff --git a/assets/icons/html.tsx b/assets/icons/html.tsx new file mode 100644 index 0000000..3aa43af --- /dev/null +++ b/assets/icons/html.tsx @@ -0,0 +1,24 @@ +"use client"; + +import type {SVGProps} from "react"; +const HTML5 = (props: SVGProps) => ( + + + + + + +); +export default HTML5; diff --git a/assets/icons/instagram.tsx b/assets/icons/instagram.tsx new file mode 100644 index 0000000..ab310d1 --- /dev/null +++ b/assets/icons/instagram.tsx @@ -0,0 +1,19 @@ +"use client"; + +import type {SVGProps} from "react"; +const Instagram = (props: SVGProps) => ( + + + +); +export default Instagram; diff --git a/assets/icons/javascript.tsx b/assets/icons/javascript.tsx new file mode 100644 index 0000000..eac993f --- /dev/null +++ b/assets/icons/javascript.tsx @@ -0,0 +1,19 @@ +"use client"; + +import type {SVGProps} from "react"; +const JavaScript = (props: SVGProps) => ( + + + + +); +export default JavaScript; diff --git a/assets/icons/linkedin.tsx b/assets/icons/linkedin.tsx new file mode 100644 index 0000000..b11e99e --- /dev/null +++ b/assets/icons/linkedin.tsx @@ -0,0 +1,19 @@ +"use client"; + +import type {SVGProps} from "react"; +const LinkedIn = (props: SVGProps) => ( + + + +); +export default LinkedIn; diff --git a/assets/icons/linux.tsx b/assets/icons/linux.tsx new file mode 100644 index 0000000..e844408 --- /dev/null +++ b/assets/icons/linux.tsx @@ -0,0 +1,375 @@ +"use client"; + +import type {SVGProps} from "react"; +const Linux = (props: SVGProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); +export default Linux; diff --git a/assets/icons/mail.tsx b/assets/icons/mail.tsx new file mode 100644 index 0000000..ce0ce41 --- /dev/null +++ b/assets/icons/mail.tsx @@ -0,0 +1,39 @@ +"use client"; + +import type {SVGProps} from "react"; +const Gmail = (props: SVGProps) => ( + + + + + + + + + + + +); +export default Gmail; diff --git a/assets/icons/medusa.tsx b/assets/icons/medusa.tsx new file mode 100644 index 0000000..37b4700 --- /dev/null +++ b/assets/icons/medusa.tsx @@ -0,0 +1,31 @@ +"use client"; + +import type {SVGProps} from "react"; +const Medusa = (props: SVGProps) => ( + + + + + + + + + +); +export default Medusa; diff --git a/assets/icons/neon.tsx b/assets/icons/neon.tsx new file mode 100644 index 0000000..9d687f6 --- /dev/null +++ b/assets/icons/neon.tsx @@ -0,0 +1,42 @@ +"use client"; + +import type {SVGProps} from "react"; +const Neon = (props: SVGProps) => ( + + + + + + + + + + + + + + + + +); +export default Neon; diff --git a/assets/icons/next.tsx b/assets/icons/next.tsx new file mode 100644 index 0000000..c9093bd --- /dev/null +++ b/assets/icons/next.tsx @@ -0,0 +1,73 @@ +"use client"; + +import type {SVGProps} from "react"; +const Next = (props: SVGProps) => ( + + + + + + + + + + + + + + + + + + + + +); +export default Next; diff --git a/assets/icons/node.tsx b/assets/icons/node.tsx new file mode 100644 index 0000000..212caed --- /dev/null +++ b/assets/icons/node.tsx @@ -0,0 +1,82 @@ +"use client"; + +import type {SVGProps} from "react"; +const Node = (props: SVGProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); +export default Node; diff --git a/assets/icons/payload.tsx b/assets/icons/payload.tsx new file mode 100644 index 0000000..d14f029 --- /dev/null +++ b/assets/icons/payload.tsx @@ -0,0 +1,19 @@ +"use client"; + +import type {SVGProps} from "react"; +const PayloadCMS = (props: SVGProps) => ( + + + +); +export default PayloadCMS; diff --git a/assets/icons/postgresql.tsx b/assets/icons/postgresql.tsx new file mode 100644 index 0000000..ffaa795 --- /dev/null +++ b/assets/icons/postgresql.tsx @@ -0,0 +1,28 @@ +"use client"; + +import type {SVGProps} from "react"; +const PostgreSQL = (props: SVGProps) => ( + + + + + + +); +export default PostgreSQL; diff --git a/assets/icons/prisma.tsx b/assets/icons/prisma.tsx new file mode 100644 index 0000000..e4c9c00 --- /dev/null +++ b/assets/icons/prisma.tsx @@ -0,0 +1,19 @@ +"use client"; + +import type {SVGProps} from "react"; +const Prisma = (props: SVGProps) => ( + + + +); +export default Prisma; diff --git a/assets/icons/react.tsx b/assets/icons/react.tsx new file mode 100644 index 0000000..6453bc3 --- /dev/null +++ b/assets/icons/react.tsx @@ -0,0 +1,19 @@ +"use client"; + +import type {SVGProps} from "react"; +const React = (props: SVGProps) => ( + + + +); +export default React; diff --git a/assets/icons/redis.tsx b/assets/icons/redis.tsx new file mode 100644 index 0000000..91e5b3f --- /dev/null +++ b/assets/icons/redis.tsx @@ -0,0 +1,45 @@ +"use client"; + +import type {SVGProps} from "react"; +const Redis = (props: SVGProps) => ( + + + + + + + + + + + +); +export default Redis; diff --git a/assets/icons/sanity.tsx b/assets/icons/sanity.tsx new file mode 100644 index 0000000..15129bf --- /dev/null +++ b/assets/icons/sanity.tsx @@ -0,0 +1,42 @@ +"use client"; + +import type {SVGProps} from "react"; +const Sanity = (props: SVGProps) => ( + + + + + + + + + + + +); +export default Sanity; diff --git a/assets/icons/sketch.tsx b/assets/icons/sketch.tsx new file mode 100644 index 0000000..145fd5e --- /dev/null +++ b/assets/icons/sketch.tsx @@ -0,0 +1,20 @@ +"use client"; + +import type {SVGProps} from "react"; +const Sketch = (props: SVGProps) => ( + + + +); +export default Sketch; diff --git a/assets/icons/spotify.tsx b/assets/icons/spotify.tsx new file mode 100644 index 0000000..ad35f94 --- /dev/null +++ b/assets/icons/spotify.tsx @@ -0,0 +1,19 @@ +"use client"; + +import type {SVGProps} from "react"; +const Spotify = (props: SVGProps) => ( + + + +); +export default Spotify; diff --git a/assets/icons/sql.tsx b/assets/icons/sql.tsx new file mode 100644 index 0000000..1380bb8 --- /dev/null +++ b/assets/icons/sql.tsx @@ -0,0 +1,20 @@ +"use client"; + +import type {SVGProps} from "react"; +const MySQL = (props: SVGProps) => ( + + + + +); +export default MySQL; diff --git a/assets/icons/studio.tsx b/assets/icons/studio.tsx new file mode 100644 index 0000000..daefa4f --- /dev/null +++ b/assets/icons/studio.tsx @@ -0,0 +1,42 @@ +"use client"; + +import type {SVGProps} from "react"; +const VisualStudio = (props: SVGProps) => ( + + + + + + + + + + + + + +); +export default VisualStudio; diff --git a/assets/icons/tailwind.tsx b/assets/icons/tailwind.tsx new file mode 100644 index 0000000..f13c7f1 --- /dev/null +++ b/assets/icons/tailwind.tsx @@ -0,0 +1,31 @@ +'use client' + +import type {SVGProps} from "react"; +const TailwindCSS = (props: SVGProps) => ( + + + + + + + + + +); +export default TailwindCSS; diff --git a/assets/icons/typescript.tsx b/assets/icons/typescript.tsx new file mode 100644 index 0000000..4bb1e4c --- /dev/null +++ b/assets/icons/typescript.tsx @@ -0,0 +1,23 @@ +'use client' + +import type {SVGProps} from "react"; +const TypeScript = (props: SVGProps) => ( + + + + +); +export default TypeScript; diff --git a/assets/icons/whatsapp.tsx b/assets/icons/whatsapp.tsx new file mode 100644 index 0000000..1024ed6 --- /dev/null +++ b/assets/icons/whatsapp.tsx @@ -0,0 +1,23 @@ +"use client"; + +import type {SVGProps} from "react"; +const WhatsApp = (props: SVGProps) => ( + + + + +); +export default WhatsApp; diff --git a/assets/icons/windows.tsx b/assets/icons/windows.tsx new file mode 100644 index 0000000..31b453c --- /dev/null +++ b/assets/icons/windows.tsx @@ -0,0 +1,18 @@ +"use client"; + +import type {SVGProps} from "react"; +const Windows = (props: SVGProps) => ( + + + +); +export default Windows; diff --git a/assets/icons/x.tsx b/assets/icons/x.tsx new file mode 100644 index 0000000..5212cda --- /dev/null +++ b/assets/icons/x.tsx @@ -0,0 +1,19 @@ +"use client"; + +import type {SVGProps} from "react"; +const X = (props: SVGProps) => ( + + + +); +export default X; diff --git a/astro.config.mjs b/astro.config.mjs deleted file mode 100644 index 2c1987d..0000000 --- a/astro.config.mjs +++ /dev/null @@ -1,22 +0,0 @@ -import mdx from "@astrojs/mdx"; -// @ts-check -import { defineConfig } from "astro/config"; - -import sitemap from "@astrojs/sitemap"; - -import tailwind from "@astrojs/tailwind"; - -import react from "@astrojs/react"; - -// https://astro.build/config -export default defineConfig({ - site: "https://owenstack.github.io", - integrations: [ - mdx(), - sitemap(), - tailwind({ - applyBaseStyles: false, - }), - react(), - ], -}); diff --git a/biome.json b/biome.json index beb54d0..3867749 100644 --- a/biome.json +++ b/biome.json @@ -1,18 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/1.9.3/schema.json", - "vcs": { - "enabled": false, - "clientKind": "git", - "useIgnoreFile": false - }, - "files": { - "ignoreUnknown": false, - "ignore": [] - }, - "formatter": { - "enabled": true, - "indentStyle": "tab" - }, + "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", "organizeImports": { "enabled": true }, @@ -21,10 +8,5 @@ "rules": { "recommended": true } - }, - "javascript": { - "formatter": { - "quoteStyle": "double" - } } } diff --git a/bun.lockb b/bun.lockb index 087585e..ea70fa6 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/components.json b/components.json index 7f5a3eb..13e9fd7 100644 --- a/components.json +++ b/components.json @@ -1,20 +1,17 @@ { - "$schema": "https://ui.shadcn.com/schema.json", - "style": "new-york", - "rsc": false, - "tsx": true, - "tailwind": { - "config": "tailwind.config.mjs", - "css": "src/styles/global.css", - "baseColor": "neutral", - "cssVariables": true, - "prefix": "" - }, - "aliases": { - "components": "@/components", - "utils": "@/lib/utils", - "ui": "@/components/ui", - "lib": "@/lib", - "hooks": "@/hooks" - } -} + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "gray", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} \ No newline at end of file diff --git a/components/AboutSection.tsx b/components/AboutSection.tsx new file mode 100644 index 0000000..30e9891 --- /dev/null +++ b/components/AboutSection.tsx @@ -0,0 +1,93 @@ +"use client"; + +import { useLanguage } from "@/lib/providers"; +import { aboutSection, carouselItems } from "@/lib/data"; +import { + Carousel, + CarouselItem, + CarouselContent, + CarouselNext, + CarouselPrevious, +} from "./ui/carousel"; +import Image from "next/image"; +import { header } from "@/lib/constants"; + +export default function AboutSection() { + const { language } = useLanguage(); + + return ( +
+
+
+
+

+ {language === "EN" + ? aboutSection.title.en + : aboutSection.title.fr} +

+

+ {language === "EN" + ? aboutSection.description.en + : aboutSection.description.fr} +

+
+ + + {language === "EN" + ? carouselItems.items.map((item, index) => ( + +
+ about me +
+

+ {item.title.en} +

+

+ {item.description.en} +

+
+
+
+ )) + : carouselItems.items.map((item, index) => ( + +
+ about me +
+

+ {item.title.fr} +

+

+ {item.description.fr} +

+
+
+
+ ))} +
+ + +
+
+
+
+ ); +} diff --git a/components/BlogSection.tsx b/components/BlogSection.tsx new file mode 100644 index 0000000..892f7ac --- /dev/null +++ b/components/BlogSection.tsx @@ -0,0 +1,38 @@ +"use client"; + +import { useLanguage } from "@/lib/providers"; +import { blogSection } from "@/lib/data"; +import { header } from "@/lib/constants"; +import TopPosts from "./TopPosts"; +import type { OstDocument } from "outstatic"; + +export default function BlogSection({ posts }: { posts: OstDocument[] }) { + const { language } = useLanguage(); + + return ( +
+
+
+
+
+ {language === "EN" + ? blogSection.subtitle.en + : blogSection.subtitle.fr} +
+

+ {language === "EN" ? blogSection.title.en : blogSection.title.fr} +

+

+ {language === "EN" + ? blogSection.description.en + : blogSection.description.fr} +

+
+ +
+
+
+ ); +} diff --git a/components/BlogSectionWrapper.tsx b/components/BlogSectionWrapper.tsx new file mode 100644 index 0000000..41233c5 --- /dev/null +++ b/components/BlogSectionWrapper.tsx @@ -0,0 +1,7 @@ +import { FetchTopPosts } from "./FetchTopPosts"; +import BlogSection from "./BlogSection"; + +export default async function BlogSectionWrapper() { + const posts = await FetchTopPosts(); + return ; +} diff --git a/components/ContactSection.tsx b/components/ContactSection.tsx new file mode 100644 index 0000000..772df7c --- /dev/null +++ b/components/ContactSection.tsx @@ -0,0 +1,175 @@ +"use client"; + +import { Button } from "./ui/button"; +import { toast } from "sonner"; +import { useTransition } from "react"; +import { useLanguage } from "@/lib/providers"; +import { contactSection, formDetails, buttonLabels } from "@/lib/data"; +import { Label } from "./ui/label"; +import { Input } from "./ui/input"; +import { Textarea } from "./ui/textarea"; +import Link from "next/link"; +import Github from "@/assets/icons/github"; +import Instagram from "@/assets/icons/instagram"; +import LinkedIn from "@/assets/icons/linkedin"; +import Gmail from "@/assets/icons/mail"; +import WhatsApp from "@/assets/icons/whatsapp"; +import X from "@/assets/icons/x"; +import { header } from "@/lib/constants"; + +export default function ContactSection() { + const { language } = useLanguage(); + const [pending, startTransition] = useTransition(); + + async function handleSubmit(event: React.FormEvent) { + event.preventDefault(); + + const form = event.currentTarget; + const formData = new FormData(form); + + startTransition(async () => { + await fetch("/api/contact", { + method: "POST", + body: formData, + }).then((response) => { + if (!response.ok) { + if (!response.ok) { + toast( + `${ + language === "EN" + ? formDetails.error.title.en + : formDetails.error.title.fr + }`, + { + description: `${ + language === "EN" + ? formDetails.error.description.en + : formDetails.error.description.fr + }`, + }, + ); + } else { + toast( + `${ + language === "EN" + ? formDetails.success.title.en + : formDetails.success.title.fr + }`, + { + description: `${ + language === "EN" + ? formDetails.success.description.en + : formDetails.success.description.fr + }`, + }, + ); + form.reset(); + } + } + }); + }); + } + + return ( +
+
+
+
+
+ {language === "EN" + ? contactSection.subtitle.en + : contactSection.subtitle.fr} +
+

+ {language === "EN" + ? contactSection.title.en + : contactSection.title.fr} +

+

+ {language === "EN" + ? contactSection.description.en + : contactSection.description.fr} +

+
+
+
+
+ + +
+
+ + +
+
+ +