diff --git a/public/images/sprint/account-settings.png b/public/images/sprint/account-settings.png new file mode 100644 index 00000000..9d942433 Binary files /dev/null and b/public/images/sprint/account-settings.png differ diff --git a/public/images/sprint/create-issue.png b/public/images/sprint/create-issue.png new file mode 100644 index 00000000..15e99b68 Binary files /dev/null and b/public/images/sprint/create-issue.png differ diff --git a/public/images/sprint/filter-status.png b/public/images/sprint/filter-status.png new file mode 100644 index 00000000..b63e617b Binary files /dev/null and b/public/images/sprint/filter-status.png differ diff --git a/public/images/sprint/landing-1.png b/public/images/sprint/landing-1.png new file mode 100644 index 00000000..ba31023a Binary files /dev/null and b/public/images/sprint/landing-1.png differ diff --git a/public/images/sprint/landing-2.png b/public/images/sprint/landing-2.png new file mode 100644 index 00000000..0e22bd8f Binary files /dev/null and b/public/images/sprint/landing-2.png differ diff --git a/public/images/sprint/landing-3.png b/public/images/sprint/landing-3.png new file mode 100644 index 00000000..1e303a29 Binary files /dev/null and b/public/images/sprint/landing-3.png differ diff --git a/public/images/sprint/landing-4.png b/public/images/sprint/landing-4.png new file mode 100644 index 00000000..d7821238 Binary files /dev/null and b/public/images/sprint/landing-4.png differ diff --git a/public/images/sprint/landing-light-1.png b/public/images/sprint/landing-light-1.png new file mode 100644 index 00000000..3e4a88dc Binary files /dev/null and b/public/images/sprint/landing-light-1.png differ diff --git a/public/images/sprint/landing-light-2.png b/public/images/sprint/landing-light-2.png new file mode 100644 index 00000000..0bf56746 Binary files /dev/null and b/public/images/sprint/landing-light-2.png differ diff --git a/public/images/sprint/landing-light-3.png b/public/images/sprint/landing-light-3.png new file mode 100644 index 00000000..5a56922c Binary files /dev/null and b/public/images/sprint/landing-light-3.png differ diff --git a/public/images/sprint/landing-light-4.png b/public/images/sprint/landing-light-4.png new file mode 100644 index 00000000..e7f26d50 Binary files /dev/null and b/public/images/sprint/landing-light-4.png differ diff --git a/public/images/sprint/old/issue-creation.png b/public/images/sprint/old/issue-creation.png new file mode 100644 index 00000000..33face67 Binary files /dev/null and b/public/images/sprint/old/issue-creation.png differ diff --git a/public/images/sprint/old/issue-detail-pane-assignee-selection.png b/public/images/sprint/old/issue-detail-pane-assignee-selection.png new file mode 100644 index 00000000..e14ca84d Binary files /dev/null and b/public/images/sprint/old/issue-detail-pane-assignee-selection.png differ diff --git a/public/images/sprint/old/main-interface.png b/public/images/sprint/old/main-interface.png new file mode 100644 index 00000000..629b0a38 Binary files /dev/null and b/public/images/sprint/old/main-interface.png differ diff --git a/public/images/sprint/old/organisation-management.png b/public/images/sprint/old/organisation-management.png new file mode 100644 index 00000000..d9de9806 Binary files /dev/null and b/public/images/sprint/old/organisation-management.png differ diff --git a/public/images/sprint/old/selection.png b/public/images/sprint/old/selection.png new file mode 100644 index 00000000..72d623dc Binary files /dev/null and b/public/images/sprint/old/selection.png differ diff --git a/public/images/sprint/old/server-configuration.png b/public/images/sprint/old/server-configuration.png new file mode 100644 index 00000000..3ee36d43 Binary files /dev/null and b/public/images/sprint/old/server-configuration.png differ diff --git a/public/images/sprint/organisations-edit.png b/public/images/sprint/organisations-edit.png new file mode 100644 index 00000000..fb784d31 Binary files /dev/null and b/public/images/sprint/organisations-edit.png differ diff --git a/public/images/sprint/organisations-features-settings.png b/public/images/sprint/organisations-features-settings.png new file mode 100644 index 00000000..32ada219 Binary files /dev/null and b/public/images/sprint/organisations-features-settings.png differ diff --git a/public/images/sprint/organisations-issues-settings.png b/public/images/sprint/organisations-issues-settings.png new file mode 100644 index 00000000..25d18e80 Binary files /dev/null and b/public/images/sprint/organisations-issues-settings.png differ diff --git a/public/images/sprint/organisations-projects-settings.png b/public/images/sprint/organisations-projects-settings.png new file mode 100644 index 00000000..230c384d Binary files /dev/null and b/public/images/sprint/organisations-projects-settings.png differ diff --git a/public/images/sprint/selected-issue.png b/public/images/sprint/selected-issue.png new file mode 100644 index 00000000..f160d338 Binary files /dev/null and b/public/images/sprint/selected-issue.png differ diff --git a/public/images/sprint/sprints.png b/public/images/sprint/sprints.png new file mode 100644 index 00000000..494a54fe Binary files /dev/null and b/public/images/sprint/sprints.png differ diff --git a/public/sprint-icon.svg b/public/sprint-icon.svg new file mode 100644 index 00000000..6bcba19c --- /dev/null +++ b/public/sprint-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 3be5fba7..3f516635 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,121 @@ +import { Link, Route, Routes, useParams } from "react-router-dom"; import { ThemeToggle } from "@/components/theme-toggle"; +import { cn } from "@/lib/utils"; +import { type ProjectMetadata, projectList, projects } from "@/projects"; function App() { return ( -
-

Oliver Bryan

- -
+ + } /> + } /> + } /> + ); } export default App; + +function Home() { + return ( +
+
+

Oliver Bryan

+ +
+
+ {projectList.map((project) => ( + + ))} +
+
+ ); +} + +function ProjectRoute() { + const { slug } = useParams(); + if (!slug || !projects[slug]) return ; + + const { Component } = projects[slug]; + return ; +} + +function NotFound() { + return ( +
+

Not found

+ + Go home + +
+ ); +} + +type ProjectListItemProps = { + metadata: ProjectMetadata; + isDevMode?: boolean; + isHidden?: boolean; +}; + +function ProjectListItem({ + metadata, + isDevMode = false, + isHidden = false, +}: ProjectListItemProps) { + const tags = metadata.tags ? [...metadata.tags].sort() : []; + const isDevHidden = isDevMode && isHidden; + + return ( + +
+
+ {metadata.image ? ( + {`${metadata.title} + ) : ( +
+ )} +
+
+

+ {metadata.title} +

+

+ {metadata.description} +

+ {tags.length > 0 ? ( +
+ {tags.map((tag) => ( + + {tag} + + ))} +
+ ) : null} +
+
+
+

+ {metadata.date} +

+
+ + ); +} diff --git a/src/main.tsx b/src/main.tsx index d70f50fd..409e6d93 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,5 +1,6 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; +import { BrowserRouter } from "react-router-dom"; import { ThemeProvider } from "@/components/theme-provider"; import "./index.css"; import App from "./App.tsx"; @@ -9,7 +10,9 @@ if (!root) throw new Error("Failed to find the root element"); createRoot(root).render( - + + + , ); diff --git a/src/projects/index.ts b/src/projects/index.ts new file mode 100644 index 00000000..32f2cf10 --- /dev/null +++ b/src/projects/index.ts @@ -0,0 +1,29 @@ +import type { ComponentType } from "react"; +import { SprintProject, metadata as sprintMetadata } from "./sprint"; + +export type ProjectMetadata = { + title: string; + description: string; + date: string; + slug: string; + image?: string | null; + url?: string; + github?: string; + hidden: boolean; + tags?: string[]; + type: string; +}; + +export type ProjectEntry = { + metadata: ProjectMetadata; + Component: ComponentType; +}; + +export const projects = { + [sprintMetadata.slug]: { + metadata: sprintMetadata, + Component: SprintProject, + }, +} satisfies Record; + +export const projectList = Object.values(projects); diff --git a/src/projects/shared/Demo.tsx b/src/projects/shared/Demo.tsx new file mode 100644 index 00000000..d9643fce --- /dev/null +++ b/src/projects/shared/Demo.tsx @@ -0,0 +1,31 @@ +import type { ReactNode } from "react"; +import { cn } from "@/lib/utils"; + +type DemoProps = { + image: string; + title: string; + type?: "boxed" | "plain"; + children?: ReactNode; +}; + +export function Demo({ image, title, type = "plain", children }: DemoProps) { + return ( +
+ {title} +
+ {title} + {children} +
+
+ ); +} diff --git a/src/projects/shared/ProjectPage.tsx b/src/projects/shared/ProjectPage.tsx new file mode 100644 index 00000000..1f00d528 --- /dev/null +++ b/src/projects/shared/ProjectPage.tsx @@ -0,0 +1,95 @@ +import type { ReactNode } from "react"; +import type { ProjectMetadata } from "@/projects"; + +type ProjectPageProps = { + metadata: ProjectMetadata; + children: ReactNode; +}; + +export function ProjectPage({ metadata, children }: ProjectPageProps) { + const tags = metadata.tags ? [...metadata.tags].sort() : []; + + return ( +
+
+
+

+ {metadata.title} +

+ {metadata.image ? ( + {`${metadata.title} + ) : ( +
+ )} +
+
+ {metadata.url ? ( + + Try {metadata.title} + + ) : null} +
+
+ +

+ {metadata.date} + {metadata.github ? ( + <> + {" "} + -{" "} + + Source Code + + + ) : null} +

+ + {tags.length > 0 ? ( +
+ {tags.map((tag: string) => ( + + {tag} + + ))} +
+ ) : null} + +
{children}
+ +

+ Oliver Bryan - {metadata.date} + {metadata.github ? ( + <> + {" "} + -{" "} + + Source Code + + + ) : null} +

+
+ ); +} diff --git a/src/projects/sprint/index.tsx b/src/projects/sprint/index.tsx new file mode 100644 index 00000000..93966c8c --- /dev/null +++ b/src/projects/sprint/index.tsx @@ -0,0 +1,148 @@ +import { Demo } from "@/projects/shared/Demo"; +import { ProjectPage } from "@/projects/shared/ProjectPage"; + +export const metadata = { + title: "Sprint", + description: + "A simple project management tool for developers. Born out of frustration with Jira.", + date: "December 2025 - Present", + slug: "sprint", + image: "/sprint-icon.svg", + url: "https://sprintpm.org", + github: "https://github.com/hex248/sprint", + hidden: false, + tags: [ + "Web", + "React", + "TypeScript", + "Tauri", + "PostgreSQL", + "Databases", + "Bun", + ], + type: "personal", +}; + +export function SprintProject() { + return ( + +

+ Sprint is a lightweight, self-hostable project management tool built for + developers who want simplicity over complexity. Frustrated with bloated + tools like Jira, I created Sprint to focus on what matters: tracking + tasks within organisations and projects without the overhead. Deploy it + on your own infrastructure for full control over your data, and access + it via the web or as a native desktop application via Tauri. +

+ +
+
+

+ Key features +

+
    +
  • Organisation and project management
  • +
  • Issue creation with titles and descriptions
  • +
  • Issue assignment to team members
  • +
  • Time tracking with start, pause, and resume timers
  • +
  • Sprint management with date ranges
  • +
  • Customizable issue statuses per organisation
  • +
  • Resizable split-pane interface
  • +
  • Role-based access: owner, admin, member
  • +
  • Avatar uploads with S3 storage
  • +
  • Native desktop app via Tauri
  • +
+
+ +
+

+ Technologies +

+
    +
  • React + TypeScript (frontend)
  • +
  • Bun.serve + Drizzle ORM (backend)
  • +
  • PostgreSQL
  • +
  • Tailwind + shadcn/ui
  • +
  • Tauri (desktop)
  • +
  • S3 file storage (avatars)
  • +
+
+
+ +
+

+ Architecture +

+

+ Sprint uses a monorepo structure with three packages: a shared package + containing database schemas and types, a Bun.serve API with Drizzle + ORM and auth middleware, and a React frontend that runs as a web app + or is bundled as a native desktop application with Tauri. +

+
+ +
+

+ Screenshots +

+
+ + + + + + + + + + + +
+
+
+ ); +}