all in one
This commit is contained in:
parent
31a126572b
commit
884b72a257
@ -3,6 +3,7 @@ title: Multi-tenant knowledge base website based on Google APIs
|
|||||||
goal: Create a modern multi-tenant web app that lets users use their Google Drive as a knowledge base
|
goal: Create a modern multi-tenant web app that lets users use their Google Drive as a knowledge base
|
||||||
role: Design and implement the web app
|
role: Design and implement the web app
|
||||||
date: Jul 29, 2023 - Nov 5, 2023
|
date: Jul 29, 2023 - Nov 5, 2023
|
||||||
|
z: 1
|
||||||
---
|
---
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
8
_content/cases/wellsfargo.md
Normal file
8
_content/cases/wellsfargo.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Wells Fargo Open Banking APIs integration
|
||||||
|
goal:
|
||||||
|
role:
|
||||||
|
date: Feb, 2021 - Aug, 2021
|
||||||
|
z: 3
|
||||||
|
draft: true
|
||||||
|
---
|
28
new.ts
Normal file
28
new.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { baseDir, getAllContent } from "@/app/lib/content";
|
||||||
|
import fs from "fs"
|
||||||
|
|
||||||
|
const args = process.argv.slice(2)
|
||||||
|
|
||||||
|
const path = args[0]
|
||||||
|
|
||||||
|
if (!path) {
|
||||||
|
throw new Error("Path is needed!")
|
||||||
|
}
|
||||||
|
|
||||||
|
const slug = path.split("/");
|
||||||
|
const t = slug[slug.length - 1]
|
||||||
|
|
||||||
|
const nextZ = Math.max.apply(Math, getAllContent().map(c => Number(c.data.z))) + 1
|
||||||
|
|
||||||
|
const meta = (title: string = t, goal: string = "", role: string = "", date: string = "", z: number = nextZ) => `---
|
||||||
|
title: ${title}
|
||||||
|
goal: ${goal}
|
||||||
|
role: ${role}
|
||||||
|
date: ${date}
|
||||||
|
z: ${z}
|
||||||
|
draft: true
|
||||||
|
---
|
||||||
|
`
|
||||||
|
|
||||||
|
fs.writeFileSync(`${baseDir}${path}.md`, meta(), {flag: "w+"})
|
||||||
|
|
@ -6,7 +6,8 @@
|
|||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint",
|
||||||
|
"new": "bun new.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.4.2",
|
"@fortawesome/fontawesome-svg-core": "^6.4.2",
|
||||||
|
@ -23,56 +23,47 @@ export default function Content({ params }: Props) {
|
|||||||
const imgSize = 1024;
|
const imgSize = 1024;
|
||||||
const { data, content } = getContent(params.slug);
|
const { data, content } = getContent(params.slug);
|
||||||
|
|
||||||
const title = () => {
|
const title = () =>
|
||||||
return (
|
<span className="text-3xl">
|
||||||
<span className="text-3xl">
|
{data.title}
|
||||||
{data.title}
|
</span>
|
||||||
</span>
|
const goal = () =>
|
||||||
)
|
data.goal ?
|
||||||
}
|
|
||||||
const goal = () => {
|
|
||||||
const g = data.goal
|
|
||||||
return g ?
|
|
||||||
(
|
(
|
||||||
<div>
|
<div>
|
||||||
<h2>The goal</h2>
|
<h2>The goal</h2>
|
||||||
{g}
|
{data.goal}
|
||||||
</div>
|
</div>
|
||||||
) :
|
) :
|
||||||
""
|
""
|
||||||
}
|
const role = () =>
|
||||||
const role = () => {
|
data.role ?
|
||||||
const r = data.role
|
|
||||||
return r ?
|
|
||||||
(
|
(
|
||||||
<div>
|
<div>
|
||||||
<h2>My role</h2>
|
<h2>My role</h2>
|
||||||
{r}
|
{data.role}
|
||||||
</div>
|
</div>
|
||||||
) :
|
) :
|
||||||
""
|
""
|
||||||
}
|
|
||||||
|
|
||||||
const ctnt = () => {
|
const ctnt = () =>
|
||||||
return (
|
<Markdown
|
||||||
<Markdown
|
className={styles.md}
|
||||||
className={styles.md}
|
remarkPlugins={[remarkGfm, remarkFrontmatter]}
|
||||||
remarkPlugins={[remarkGfm, remarkFrontmatter]}
|
rehypePlugins={[rehypeRaw, rehypeHighlight]}
|
||||||
rehypePlugins={[rehypeRaw, rehypeHighlight]}
|
components={{
|
||||||
components={{
|
img({ height, width, src, alt }) {
|
||||||
img({ height, width, src, alt }) {
|
return (
|
||||||
return (
|
<span className="w-full h-max p-20">
|
||||||
<span className="w-full h-max p-20">
|
<Image className="w-full h-full" alt={alt!} height={Number(height) || imgSize} width={Number(width) || imgSize} src={`${data.slug}${src}`}></Image>
|
||||||
<Image className="w-full h-full" alt={alt!} height={Number(height) || imgSize} width={Number(width) || imgSize} src={`${data.slug}${src}`}></Image>
|
</span>
|
||||||
</span>
|
)
|
||||||
)
|
}
|
||||||
}
|
}}
|
||||||
}}
|
>
|
||||||
>
|
{content}
|
||||||
{content}
|
</Markdown>
|
||||||
</Markdown>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full p-20 overflow-x-hidden overflow-scroll">
|
<div className="w-full h-full p-20 overflow-x-hidden overflow-scroll">
|
||||||
<div className="flex flex-col gap-4 text-center">
|
<div className="flex flex-col gap-4 text-center">
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import Cases from "$components/cases";
|
import Cases from "$components/cases";
|
||||||
|
|
||||||
export default function CasesPage() {
|
const CasesPage = () => <Cases />
|
||||||
return (
|
|
||||||
<Cases />
|
export default CasesPage
|
||||||
)
|
|
||||||
}
|
|
||||||
|
@ -2,24 +2,16 @@ import { GrayMatterFile } from "gray-matter";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { getCases } from "../lib/content";
|
import { getCases } from "../lib/content";
|
||||||
|
|
||||||
export default function Cases() {
|
const cases: GrayMatterFile<string>[] = getCases()
|
||||||
const cases: GrayMatterFile<string>[] = getCases()
|
const Cases = () =>
|
||||||
return (
|
<div className="p-20 w-3/4 mx-auto flex flex-col gap-4">
|
||||||
<div className="p-20 w-3/4 mx-auto">
|
{cases.filter(c => !c.data.draft).sort(c => c.data.z).map((c) => c.data).map((d) =>
|
||||||
{cases.map((c) => {
|
<div key={d.slug} className="w-full h-max flex justify-center">
|
||||||
const d = c.data;
|
<Link className="btn flex flex-col w-full text-center" href={d.slug}>
|
||||||
const date = d.date.split("-")
|
<span className="text-lg px-6">{d.title}</span>
|
||||||
const from = date[0]?.trim()
|
<span>{d.date}</span>
|
||||||
const to = date[1]?.trim()
|
</Link>
|
||||||
return (
|
</div>
|
||||||
<div key={d.slug} className="w-full h-max flex justify-center">
|
)}
|
||||||
<Link className="btn flex flex-col w-full text-center" href={d.slug}>
|
</div>
|
||||||
<span className="text-lg px-6">{d.title}</span>
|
export default Cases;
|
||||||
{from} {to ? `- ${to}` : ""}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
@ -2,18 +2,16 @@ import { faGithub, faGitlab } from "@fortawesome/free-brands-svg-icons";
|
|||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
export default function Links() {
|
const Links = () =>
|
||||||
|
<div className="grid w-full h-full place-content-center">
|
||||||
return (
|
<div className={"grid grid-cols-2 gap-4 place-content-center"}>
|
||||||
<div className="grid w-full h-full place-content-center">
|
<Link aria-label="GitHub" href={process.env.NEXT_PUBLIC_GITHUB_URL!} target="_blank">
|
||||||
<div className={"grid grid-cols-2 gap-4 place-content-center"}>
|
<FontAwesomeIcon icon={faGithub} />
|
||||||
<Link aria-label="GitHub" href={process.env.NEXT_PUBLIC_GITHUB_URL!} target="_blank">
|
</Link>
|
||||||
<FontAwesomeIcon icon={faGithub} />
|
<Link aria-label="GitLab" href={process.env.NEXT_PUBLIC_GITLAB_URL!} target="_blank">
|
||||||
</Link>
|
<FontAwesomeIcon icon={faGitlab} />
|
||||||
<Link aria-label="GitLab" href={process.env.NEXT_PUBLIC_GITLAB_URL!} target="_blank">
|
</Link>
|
||||||
<FontAwesomeIcon icon={faGitlab} />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
</div>
|
||||||
}
|
|
||||||
|
export default Links
|
||||||
|
@ -2,24 +2,21 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
|
|
||||||
export default function Navbar() {
|
const link = (text: string, href: string) => {
|
||||||
const path = usePathname()
|
const path = usePathname()
|
||||||
|
|
||||||
const link = (text: string, href: string) => {
|
|
||||||
return (
|
|
||||||
<Link data-selected={path === href} className="btn" aria-label={text} href={href}>
|
|
||||||
{text}
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-max h-max px-6 py-2 mx-auto rounded-full bg-slate-900 grid place-content-center">
|
<Link data-selected={path === href} className="btn" aria-label={text} href={href}>
|
||||||
<div className="flex flex-row gap-6">
|
{text}
|
||||||
{link("Home", "/")}
|
</Link>
|
||||||
{link("Cases", "/cases")}
|
|
||||||
{link("Contact", "/contact")}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
const Navbar = () =>
|
||||||
|
<div className="w-max h-max px-6 py-2 mx-auto rounded-full bg-slate-900 grid place-content-center">
|
||||||
|
<div className="flex flex-row gap-6">
|
||||||
|
{link("Home", "/")}
|
||||||
|
{link("Cases", "/cases")}
|
||||||
|
{link("Contact", "/contact")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
export default Navbar
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
import { faEnvelope } from "@fortawesome/free-solid-svg-icons";
|
import { faEnvelope } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
export default function Contact() {
|
const email = "ivan@idimitrov.dev";
|
||||||
|
const mailto = `mailto:${email}`
|
||||||
const email = "ivan@idimitrov.dev";
|
const Contact = () =>
|
||||||
const mailto = `mailto:${email}`
|
<div className="w-full h-full p-2 grid place-content-center">
|
||||||
|
<div className="flex flex-row gap-4">
|
||||||
return (
|
<a aria-label={mailto} href={mailto}><FontAwesomeIcon icon={faEnvelope} /></a>
|
||||||
<div className="w-full h-full p-2 grid place-content-center">
|
|
||||||
<div className="flex flex-row gap-4">
|
|
||||||
<a aria-label={mailto} href={mailto}><FontAwesomeIcon icon={faEnvelope} /></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
</div>
|
||||||
}
|
|
||||||
|
export default Contact
|
||||||
|
@ -2,7 +2,7 @@ import fs from "fs";
|
|||||||
import matter, { GrayMatterFile } from "gray-matter";
|
import matter, { GrayMatterFile } from "gray-matter";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
const baseDir = "./_content/"
|
export const baseDir = "./_content/"
|
||||||
|
|
||||||
export const getContent = (slug: string[]): GrayMatterFile<string> => {
|
export const getContent = (slug: string[]): GrayMatterFile<string> => {
|
||||||
let p = path.join(baseDir)
|
let p = path.join(baseDir)
|
||||||
@ -17,13 +17,10 @@ export const getContent = (slug: string[]): GrayMatterFile<string> => {
|
|||||||
|
|
||||||
const getAllPathsRecursive = (base = baseDir): string[] => {
|
const getAllPathsRecursive = (base = baseDir): string[] => {
|
||||||
let results = [] as string[];
|
let results = [] as string[];
|
||||||
|
|
||||||
const files = fs.readdirSync(base);
|
const files = fs.readdirSync(base);
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const filePath = path.join(base, file);
|
const filePath = path.join(base, file);
|
||||||
const stat = fs.statSync(filePath);
|
const stat = fs.statSync(filePath);
|
||||||
|
|
||||||
if (stat.isDirectory()) {
|
if (stat.isDirectory()) {
|
||||||
results = results.concat(getAllPathsRecursive(filePath));
|
results = results.concat(getAllPathsRecursive(filePath));
|
||||||
} else if (path.extname(filePath) === '.md') {
|
} else if (path.extname(filePath) === '.md') {
|
||||||
@ -33,11 +30,9 @@ const getAllPathsRecursive = (base = baseDir): string[] => {
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAllPaths = (base = baseDir): string[] => {
|
export const getAllPaths = (base = baseDir): string[] => getAllPathsRecursive(base).map(p => p.substring(9))
|
||||||
return getAllPathsRecursive(base).map(p => p.substring(9))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getCases = (): GrayMatterFile<string>[] => {
|
export const getCases = (): GrayMatterFile<string>[] => getAllPaths(`${baseDir}cases/`).map(s => s.split("/")).map(getContent)
|
||||||
return getAllPaths(`${baseDir}cases/`).map(s => s.split("/")).map(getContent)
|
|
||||||
}
|
export const getAllContent = (): GrayMatterFile<string>[] => getAllPaths().map(s => s.split("/")).map(getContent)
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import Links from "$components/links";
|
import Links from "$components/links";
|
||||||
|
|
||||||
export default function Home() {
|
const Home = () => <Links />
|
||||||
return (
|
|
||||||
<Links />
|
export default Home;
|
||||||
)
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user