Loading...
}
{error ? (
diff --git a/src/components/LanguagesChart.jsx b/src/components/LanguagesChart.jsx
new file mode 100644
index 0000000..b54d5f2
--- /dev/null
+++ b/src/components/LanguagesChart.jsx
@@ -0,0 +1,29 @@
+import MetricBar from "./MetricBar"
+
+const LanguagesChart = ({ chartData, error }) => {
+ return (
+
+
+ programming languages
+
+
+ {error ? (
+
Data could not be found!
+ ) : !chartData?.length ? (
+
No data for time period.
+ ) : (
+ chartData.map((x) => (
+
+ ))
+ )}
+
+ )
+}
+
+export default LanguagesChart
diff --git a/src/components/MetricBar.jsx b/src/components/MetricBar.jsx
new file mode 100644
index 0000000..1640d10
--- /dev/null
+++ b/src/components/MetricBar.jsx
@@ -0,0 +1,37 @@
+const MetricBar = ({ metric, hours, percentage, color }) => (
+
+
+ {metric}
+
+ {hours}h ({percentage}%)
+
+
+
+
+)
+
+export default MetricBar
diff --git a/src/components/ProjectsChart.jsx b/src/components/ProjectsChart.jsx
new file mode 100644
index 0000000..44a8274
--- /dev/null
+++ b/src/components/ProjectsChart.jsx
@@ -0,0 +1,30 @@
+import MetricBar from "./MetricBar"
+
+const ProjectsChart = ({ chartData, error }) => {
+ return (
+
+
projects
+
+ {error ? (
+
Data could not be found!
+ ) : !chartData?.length ? (
+
No data for time period.
+ ) : (
+ chartData.map((x) => (
+
+ ))
+ )}
+
+ Data excludes workplace repos.
+
+
+ )
+}
+
+export default ProjectsChart
diff --git a/src/components/Scorecard.jsx b/src/components/Scorecard.jsx
new file mode 100644
index 0000000..fa2e1dd
--- /dev/null
+++ b/src/components/Scorecard.jsx
@@ -0,0 +1,10 @@
+const Scorecard = ({ title, metric }) => {
+ return (
+
+ )
+}
+
+export default Scorecard
diff --git a/src/containers/CodeStats.jsx b/src/containers/CodeStats.jsx
new file mode 100644
index 0000000..5bcbe82
--- /dev/null
+++ b/src/containers/CodeStats.jsx
@@ -0,0 +1,109 @@
+import { useQuery } from "@tanstack/react-query"
+import wakapiApi from "../api/wakapi-api"
+import { convertDateFriendly } from "../utils/convertDate"
+import Scorecard from "../components/Scorecard"
+import LanguagesChart from "../components/LanguagesChart"
+import ProjectsChart from "../components/ProjectsChart"
+
+const convertSeconds = (secs) => {
+ return `${Math.floor(secs / 3600)}h ${Math.floor((secs % 3600) / 60)}m`
+}
+
+const CodeStats = () => {
+ const { data, isLoading, error } = useQuery({
+ queryKey: ["codestats"],
+ queryFn: () =>
+ wakapiApi.get(`summary?interval=week`).then((res) => res.data),
+ })
+
+ const grandTotal = data?.projects.reduce((acc, curr) => acc + curr.total, 0)
+ const grandTotalFormatted = `${Math.floor(grandTotal / 3600)}h ${Math.floor((grandTotal % 3600) / 60)}m`
+ const os = data?.operating_systems
+ const osMetric = os ? (
+
+ {os[0]?.key}: {convertSeconds(os[0].total)}, {os[1]?.key}:{" "}
+ {convertSeconds(os[1].total)}
+
+ ) : (
+ "Error"
+ )
+
+ const personalProjects =
+ data && data?.projects.filter((project) => !project.key.includes("gp-"))
+
+ const mainProject = personalProjects?.sort((a, b) => a.total > b.total)[0].key
+
+ console.log(personalProjects)
+
+ const languagesChartData = data?.languages
+ .map((lang) => ({
+ metric: lang.key,
+ language: lang.key,
+ hours: (lang.total / 3600).toFixed(1),
+ percentage: (
+ (lang.total / data.languages.reduce((sum, i) => sum + i.total, 0)) *
+ 100
+ ).toFixed(1),
+ }))
+ .slice(0, 4)
+
+ const projectsChartData =
+ personalProjects &&
+ personalProjects.map((proj) => ({
+ metric: proj.key,
+ project: proj.key,
+ hours: (proj.total / 3600).toFixed(1),
+ percentage: (
+ (proj.total / personalProjects.reduce((sum, i) => sum + i.total, 0)) *
+ 100
+ ).toFixed(1),
+ }))
+
+ return (
+
+
+
+
+
+ code stats
+
+
+ {convertDateFriendly(data?.from)} -{" "}
+ {convertDateFriendly(data?.to)}
+
+
+ {/* Score-cards */}
+
+
+
+
+
+
+
+
+
+
+ Data sourced from my self-hosted{" "}
+
+ Wakapi
+ {" "}
+ instance.
+
+
+
+
+ )
+}
+
+export default CodeStats
diff --git a/src/containers/PostListing.jsx b/src/containers/PostListing.jsx
index 6328748..4696df8 100644
--- a/src/containers/PostListing.jsx
+++ b/src/containers/PostListing.jsx
@@ -8,7 +8,9 @@ const PostListing = ({ posts, title, showAllButton }) => {
- {title}
+
+ {title}
+
{posts.map((post) => (
-
@@ -19,7 +21,7 @@ const PostListing = ({ posts, title, showAllButton }) => {
{post.title}
diff --git a/src/index.css b/src/index.css
index 54b7570..5e8da1e 100644
--- a/src/index.css
+++ b/src/index.css
@@ -4,82 +4,86 @@
@import "tw-animate-css";
* {
- outline-color: color-mix(in srgb, var(--ring) 50%, transparent);
+ outline-color: color-mix(in srgb, var(--ring) 50%, transparent);
}
html {
- font-family: var(--font-sansserif);
+ font-family: var(--font-sansserif);
}
body {
- background-color: var(--background);
- color: var(--foreground);
+ background-color: var(--background);
+ color: var(--foreground);
}
.condensed {
- font-family: "IBM Plex Sans Condensed";
+ font-family: "IBM Plex Sans Condensed";
}
figcaption {
- font-weight: 500;
- font-family: "IBM Plex Sans Condensed";
+ font-weight: 500;
+ font-family: "IBM Plex Sans Condensed";
}
h1 {
- color: var(--color-orange-light);
+ color: var(--color-orange-light);
- font-family: "IBM Plex Sans Condensed";
+ font-family: "IBM Plex Sans Condensed";
}
h2 {
- font-family: "IBM Plex Sans Condensed";
- color: var(--color-green-light);
+ font-family: "IBM Plex Sans Condensed";
+ color: var(--color-green-light);
}
h3 {
- font-family: "IBM Plex Sans Condensed";
+ font-family: "IBM Plex Sans Condensed";
}
+
.monospaced-font {
- font-family: "iA Writer Mono";
+ font-family: "iA Writer Mono";
}
+
.scanlined {
- position: relative; /* Add this */
+ position: relative;
+ /* Add this */
}
.scanlined::after {
- content: "";
- position: absolute; /* Change from fixed */
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-image: linear-gradient(rgba(0, 0, 0, 0.4) 1px, transparent 1px);
- background-size: 2px 2px;
- background-repeat: repeat;
- pointer-events: none;
- z-index: 9999; /* Might want to lower this too */
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-image: linear-gradient(rgba(0, 0, 0, 0.4) 1px, transparent 1px);
+ background-size: 2px 2px;
+ background-repeat: repeat;
+ pointer-events: none;
+ z-index: 9999;
+ /* Might want to lower this too */
}
code {
- font-family: var(--font-monospaced);
+ font-family: var(--font-monospaced);
}
p code {
- color: var(--foreground);
- background: #504945;
- font-size: 14px;
- padding: 0.2rem 0.3rem;
- border-radius: var(--radius);
- font-weight: 500;
+ color: var(--foreground);
+ background: #504945;
+ font-size: 14px;
+ padding: 0.2rem 0.3rem;
+ border-radius: var(--radius);
+ font-weight: 500;
}
.shiki {
- padding: 1rem 1.2rem;
- border-radius: 0;
- overflow-x: auto;
- margin: 1.5rem 0;
- line-height: 1.3;
- /* counter-reset: line; */
- font-family: var(--font-monospaced) !important;
- font-size: 14px !important;
+ padding: 1rem 1.2rem;
+ border-radius: 0;
+ overflow-x: auto;
+ margin: 1.5rem 0;
+ line-height: 1.3;
+ /* counter-reset: line; */
+ font-family: var(--font-monospaced) !important;
+ font-size: 14px !important;
}
diff --git a/src/pages/home.jsx b/src/pages/home.jsx
index d175469..bd37e72 100644
--- a/src/pages/home.jsx
+++ b/src/pages/home.jsx
@@ -3,6 +3,7 @@ import PostListing from "@/containers/PostListing"
import { usePosts } from "@/hooks/usePosts"
import gruvboxComputer from "../images/gruvbox-computer.svg"
import EolasListing from "@/components/EolasListing"
+import CodeStats from "../containers/CodeStats"
// import TodayILearned from "@/containers/TodayILearned"
const HomePage = () => {
@@ -42,7 +43,7 @@ const HomePage = () => {
-
+
projects
@@ -65,7 +66,7 @@ const HomePage = () => {
-
+
)
diff --git a/src/styles/_variables.css b/src/styles/_variables.css
index b2d79b8..4f349d2 100644
--- a/src/styles/_variables.css
+++ b/src/styles/_variables.css
@@ -1,79 +1,79 @@
:root {
- --radius: 0.3rem;
- --background: #282828;
- --foreground: #ebdbb2;
- --sidebar: #3c3836;
- --color-red-light: #fb4934;
- --color-orange-light: #fe8019;
- --color-green-light: #b8bb26;
- --color-aqua-muted: #689d6a;
- --card: oklch(1 0 0);
- --card-foreground: oklch(0.145 0 0);
- --popover: oklch(1 0 0);
- --popover-foreground: oklch(0.145 0 0);
- --primary: #8ec07c;
- --primary-muted: #689d6a;
- --primary-foreground: oklch(0.985 0 0);
- --secondary: oklch(0.97 0 0);
- --secondary-foreground: oklch(0.205 0 0);
- --muted: #bdae93;
- --muted-foreground: #928374;
- --accent: oklch(0.97 0 0);
- --accent-foreground: oklch(0.205 0 0);
- --destructive: oklch(0.577 0.245 27.325);
- --border: oklch(0.922 0 0);
- --input: oklch(0.922 0 0);
- --ring: oklch(0.708 0 0);
- --chart-1: oklch(0.646 0.222 41.116);
- --chart-2: oklch(0.6 0.118 184.704);
- --chart-3: oklch(0.398 0.07 227.392);
- --chart-4: oklch(0.828 0.189 84.429);
- --chart-5: oklch(0.769 0.188 70.08);
- --sidebar-foreground: oklch(0.145 0 0);
- --sidebar-primary: oklch(0.205 0 0);
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.97 0 0);
- --sidebar-accent-foreground: oklch(0.205 0 0);
- --sidebar-border: oklch(0.922 0 0);
- --sidebar-ring: oklch(0.708 0 0);
- --font-monospaced: "IBM Plex Mono";
- --font-sansserif: "IBM Plex Sans", sans-serif;
+ --radius: 0.3rem;
+ --background: #282828;
+ --foreground: #ebdbb2;
+ --sidebar: #3c3836;
+ --color-red-light: #fb4934;
+ --color-orange-light: #fe8019;
+ --color-green-light: #b8bb26;
+ --color-aqua-muted: #689d6a;
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.145 0 0);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.145 0 0);
+ --primary: #8ec07c;
+ --primary-muted: #689d6a;
+ --primary-foreground: oklch(0.985 0 0);
+ --secondary: oklch(0.97 0 0);
+ --secondary-foreground: oklch(0.205 0 0);
+ --muted: #bdae93;
+ --muted-foreground: #928374;
+ --accent: oklch(0.97 0 0);
+ --accent-foreground: oklch(0.205 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
+ --border: oklch(0.922 0 0);
+ --input: oklch(0.922 0 0);
+ --ring: oklch(0.708 0 0);
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ --sidebar-foreground: oklch(0.145 0 0);
+ --sidebar-primary: oklch(0.205 0 0);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.97 0 0);
+ --sidebar-accent-foreground: oklch(0.205 0 0);
+ --sidebar-border: oklch(0.922 0 0);
+ --sidebar-ring: oklch(0.708 0 0);
+ --font-monospaced: "IBM Plex Mono";
+ --font-sansserif: "IBM Plex Sans", sans-serif;
}
@theme inline {
- --radius-sm: calc(var(--radius) - 4px);
- --radius-md: calc(var(--radius) - 2px);
- --radius-lg: var(--radius);
- --radius-xl: calc(var(--radius));
- --color-background: var(--background);
- --color-foreground: var(--foreground);
- --color-card: var(--card);
- --color-card-foreground: var(--card-foreground);
- --color-popover: var(--popover);
- --color-popover-foreground: var(--popover-foreground);
- --color-primary: var(--primary);
- --color-primary-foreground: var(--primary-foreground);
- --color-secondary: var(--secondary);
- --color-secondary-foreground: var(--secondary-foreground);
- --color-muted: var(--muted);
- --color-muted-foreground: var(--muted-foreground);
- --color-accent: var(--accent);
- --color-accent-foreground: var(--accent-foreground);
- --color-destructive: var(--destructive);
- --color-border: var(--border);
- --color-input: var(--input);
- --color-ring: var(--ring);
- --color-chart-1: var(--chart-1);
- --color-chart-2: var(--chart-2);
- --color-chart-3: var(--chart-3);
- --color-chart-4: var(--chart-4);
- --color-chart-5: var(--chart-5);
- --color-sidebar: var(--sidebar);
- --color-sidebar-foreground: var(--sidebar-foreground);
- --color-sidebar-primary: var(--sidebar-primary);
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
- --color-sidebar-accent: var(--sidebar-accent);
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
- --color-sidebar-border: var(--sidebar-border);
- --color-sidebar-ring: var(--sidebar-ring);
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius));
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+ --color-sidebar: var(--sidebar);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-ring: var(--sidebar-ring);
}
diff --git a/src/templates/BlogTemplate.jsx b/src/templates/BlogTemplate.jsx
index b8cb3ad..4f2afb5 100644
--- a/src/templates/BlogTemplate.jsx
+++ b/src/templates/BlogTemplate.jsx
@@ -5,41 +5,41 @@ import { convertDate } from "@/utils/convertDate"
import { usePosts } from "@/hooks/usePosts"
const BlogTemplate = () => {
- const { slug } = useParams()
- const { posts } = usePosts()
- const post = posts?.find((x) => x.slug === slug)
+ const { slug } = useParams()
+ const { posts } = usePosts()
+ const post = posts?.find((x) => x.slug === slug)
- return (
-
-
- {!post ? (
-
Loading...
- ) : (
-
-
+ return (
+
+
+ {!post ? (
+
Loading...
+ ) : (
+
+
- h2]:text-2xl [&>h2]:font-medium [&>h2]:my-4 [&>h2]:text-[#fabd2f]!
[&>h3]:text-xl [&>h3]:font-medium [&>h3]:my-4 [&>h3]:text-[#fabd2f]
[&>h4]:text-lg [&>h4]:font-medium [&>h4]:my-4 [&>h4]:text-[#fabd2f]
@@ -65,13 +65,13 @@ const BlogTemplate = () => {
[&>table>tbody>tr]:m-0 [&>table>tbody>tr]:border-t [&>table>tbody>tr]:p-0 [&>table>tbody>tr:even]:bg-muted
[&>table>tbody>tr>td]:border [&>table>tbody>tr>td]:px-4 [&>table>tbody>tr>td]:py-2 [&>table>tbody>tr>td]:text-left [&>table>tbody>tr>td[align=center]]:text-center [&>table>tbody>tr>td[align=right]]:text-right
"
- dangerouslySetInnerHTML={{ __html: post?.html }}
- />
-
- )}
-
-
- )
+ dangerouslySetInnerHTML={{ __html: post?.html }}
+ />
+
+ )}
+
+
+ )
}
export default BlogTemplate
diff --git a/src/templates/MainTemplate.jsx b/src/templates/MainTemplate.jsx
index 37b73d4..213c0d4 100644
--- a/src/templates/MainTemplate.jsx
+++ b/src/templates/MainTemplate.jsx
@@ -3,82 +3,66 @@
import gruvboxComputer from "../images/gruvbox-computer.svg"
import { Link } from "react-router"
const Header = () => {
- return (
-
-
-
- )
+ return (
+
+
+
+ )
}
const Footer = () => {
- return (
-
- )
+ return (
+
+ )
}
const MainTemplate = ({ children }) => {
- return (
-
- )
+ return (
+
+ )
}
export default MainTemplate
diff --git a/src/utils/convertDate.js b/src/utils/convertDate.js
index 5917668..fc054dd 100644
--- a/src/utils/convertDate.js
+++ b/src/utils/convertDate.js
@@ -39,4 +39,12 @@ const convertDate = (isoStamp) => {
return `${year}-${month}-${day}`
}
-export { convertDate }
+const convertDateFriendly = (isoStamp) => {
+ const unixSeconds = new Date(isoStamp)
+ const day = unixSeconds.getDate()
+ const month = months[unixSeconds.getMonth()]
+ const year = unixSeconds.getFullYear()
+ return `${day} ${month} ${year}`
+}
+
+export { convertDate, convertDateFriendly }