Use Nextra for docs (#1140)

Use https://github.com/shuding/nextra and
https://github.com/leoMirandaa/shadcn-landing-page instead of
Docusaurus.
This commit is contained in:
Erik Vroon
2025-03-01 20:41:32 +01:00
committed by GitHub
parent ce7bd48635
commit eaeccebc16
82 changed files with 5801 additions and 10551 deletions

View File

@@ -21,7 +21,7 @@ jobs:
with:
node-version: '22'
cache: 'yarn'
cache-dependency-path: frontend/yarn.lock
cache-dependency-path: docs/yarn.lock
- name: Install npm modules
run: yarn

View File

@@ -6,6 +6,9 @@ on:
branches:
- 'master'
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-24.04
@@ -18,7 +21,7 @@ jobs:
with:
node-version: '22'
cache: 'yarn'
cache-dependency-path: frontend/yarn.lock
cache-dependency-path: docs/yarn.lock
- name: Install npm modules
run: yarn

3
docs/.eslintrc.json Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": ["next/core-web-vitals", "next/typescript"]
}

36
docs/.gitignore vendored
View File

@@ -1,20 +1,32 @@
# Dependencies
# deps
/node_modules
# Production
# generated content
.contentlayer
.content-collections
.source
# test & build
/coverage
/.next/
/out/
/build
*.tsbuildinfo
# Generated files
.docusaurus
.cache-loader
# Misc
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
*.pem
/.pnp
.pnp.js
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# others
.env*.local
.vercel
next-env.d.ts
.idea
_pagefind/

View File

@@ -8,6 +8,8 @@ const config = {
code_blocks: false,
line_length: 100
},
"single-h1": false,
"no-inline-html": false,
// part of the markdownlint-rule-relative-links plugin
"relative-links": true

View File

@@ -1,45 +1,12 @@
# Website
# docs_2
This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website
generator.
This is a Next.js application generated with
Nextra and [shadcn-landing-page](https://github.com/leoMirandaa/shadcn-landing-page).
## Installation
Run development server:
```bash
yarn
yarn dev
```
## Local Development
```bash
yarn start
```
This command starts a local development server and opens up a browser window. Most changes are
reflected live without having to restart the server.
## Build
```bash
yarn build
```
This command generates static content into the `build` directory and can be served using any static
contents hosting service.
## Deployment
Using SSH:
```bash
USE_SSH=true yarn deploy
```
Not using SSH:
```bash
GIT_USER=<Your GitHub username> yarn deploy
```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and
push to the `gh-pages` branch.
Open <http://localhost:3000> with your browser to see the result.

View File

@@ -0,0 +1,27 @@
import { generateStaticParamsFor, importPage } from "nextra/pages";
import { useMDXComponents } from "../../../mdx-components";
export const generateStaticParams = generateStaticParamsFor("mdxPath");
// @ts-expect-error 123123
export async function generateMetadata(props) {
const params = await props.params;
const { metadata } = await importPage(params.mdxPath);
return metadata;
}
// @ts-expect-error 1231231
// eslint-disable-next-line react-hooks/rules-of-hooks
const Wrapper = useMDXComponents().wrapper;
// @ts-expect-error 123123
export default async function Page(props) {
const params = await props.params;
const result = await importPage(params.mdxPath);
const { default: MDXContent, toc, metadata } = result;
return (
<Wrapper toc={toc} metadata={metadata}>
<MDXContent {...props} params={params} />
</Wrapper>
);
}

65
docs/app/layout.tsx Normal file
View File

@@ -0,0 +1,65 @@
import { Layout, Navbar } from "nextra-theme-docs";
import { Head } from "nextra/components";
import { getPageMap } from "nextra/page-map";
import "nextra-theme-docs/style.css";
import logo from "../content/img/logo.svg";
import Image from "next/image";
import { Footer } from "../components/Footer";
export const metadata = {
// Define your metadata here
// For more information on metadata API, see: https://nextjs.org/docs/app/building-your-application/optimizing/metadata
};
const navbar = (
<Navbar
logo={
<>
<Image
width={36}
height={36}
src={logo.src}
className="mr-2"
alt="Preview of Bracket"
/>
<b className="text-3xl">Bracket</b>
</>
}
projectLink="https://github.com/evroon/bracket"
// ... Your additional navbar options
/>
);
// @ts-expect-error 123123213
export default async function RootLayout({ children }) {
return (
<html
// Not required, but good for SEO
lang="en"
// Required to be set
dir="ltr"
// Suggested by `next-themes` package https://github.com/pacocoursey/next-themes#with-app
suppressHydrationWarning
className="dark"
>
<Head
// ... Your additional head options
>
{/* Your additional tags should be passed as `children` of `<Head>` element */}
</Head>
<body>
<Layout
darkMode={true}
navbar={navbar}
pageMap={await getPageMap()}
docsRepositoryBase="https://github.com/evroon/bracket/tree/master/docs"
footer={<Footer />}
// ... Your additional layout options
>
{children}
</Layout>
</body>
</html>
);
}

209
docs/app/page.css Normal file
View File

@@ -0,0 +1,209 @@
@import 'tailwindcss';
@plugin 'tailwindcss-animate';
@custom-variant dark (true);
@theme {
--color-border: hsl(var(--border));
--color-input: hsl(var(--input));
--color-ring: hsl(var(--ring));
--color-background: hsl(var(--background));
--color-foreground: hsl(var(--foreground));
--color-primary: hsl(var(--primary));
--color-primary-foreground: hsl(var(--primary-foreground));
--color-secondary: hsl(var(--secondary));
--color-secondary-foreground: hsl(var(--secondary-foreground));
--color-destructive: hsl(var(--destructive));
--color-destructive-foreground: hsl(var(--destructive-foreground));
--color-muted: hsl(var(--muted));
--color-muted-foreground: hsl(var(--muted-foreground));
--color-accent: hsl(var(--accent));
--color-accent-foreground: hsl(var(--accent-foreground));
--color-popover: hsl(var(--popover));
--color-popover-foreground: hsl(var(--popover-foreground));
--color-card: hsl(var(--card));
--color-card-foreground: hsl(var(--card-foreground));
--radius-lg: var(--radius);
--radius-md: calc(var(--radius) - 2px);
--radius-sm: calc(var(--radius) - 4px);
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--radix-accordion-content-height);
}
to {
height: 0;
}
}
}
@utility container {
margin-inline: auto;
padding-inline: 1.5rem;
@media (width >= --theme(--breakpoint-sm)) {
max-width: none;
}
@media (width >= 1400px) {
max-width: 1400px;
}
}
/*
The default border color has changed to `currentColor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
}
/* *=========== Default theme =========== */
/* @layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
}
} */
/* *=========== Purple theme =========== */
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 142.1 76.2% 36.3%;
--primary-foreground: 355.7 100% 97.3%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 142.1 76.2% 36.3%;
--radius: 0.5rem;
}
.dark {
--background: 20 14.3% 4.1%;
--foreground: 0 0% 95%;
--card: 24 9.8% 10%;
--card-foreground: 0 0% 95%;
--popover: 0 0% 9%;
--popover-foreground: 0 0% 95%;
--primary: 261 70.6% 71%;
--primary-foreground: 144.9 80.4% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 15%;
--muted-foreground: 240 5% 64.9%;
--accent: 12 6.5% 15.1%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 85.7% 97.3%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 142.4 71.8% 29.2%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

22
docs/app/page.tsx Normal file
View File

@@ -0,0 +1,22 @@
import { Hero } from "../components/Hero";
import { About } from "../components/About";
import { HowItWorks } from "../components/HowItWorks";
import { Features } from "../components/Features";
import { Cta } from "../components/Cta";
import "./page.css";
import { PreviewImage } from "../components/PreviewImage";
export default function Page() {
return (
<>
<Hero />
<PreviewImage />
<About />
<HowItWorks />
<Features />
{/*<AdvancedFeatures />*/}
<Cta />
{/*<FAQ />*/}
</>
);
}

View File

@@ -1,3 +0,0 @@
module.exports = {
presets: [require.resolve("@docusaurus/core/lib/babel/preset")],
};

16
docs/components.json Normal file
View File

@@ -0,0 +1,16 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/app.css",
"baseColor": "slate",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}

34
docs/components/About.tsx Normal file
View File

@@ -0,0 +1,34 @@
export const About = () => {
return (
<section id="about" className="container py-16">
<div className="bg-muted/50 border border-border rounded-lg py-12">
<div className="px-6 flex flex-col-reverse md:flex-row gap-8 md:gap-12">
{/*<Image*/}
{/* width={100}*/}
{/* height={100}*/}
{/* src={logo.src}*/}
{/* alt=""*/}
{/* className="w-[200px] object-contain rounded-lg"*/}
{/*/>*/}
<div className="bg-green-0 flex flex-col justify-between">
<h2 className="text-3xl md:text-4xl font-bold">
<span className="bg-linear-to-b from-primary/70 to-primary text-transparent bg-clip-text">
About{" "}
</span>
Bracket
</h2>
<p className="text-xl text-muted-foreground mt-4">
There are many tournament management systems available online.
However, only few (if any) are open-source and free to use, while
still being feature-rich. Bracket aims to fill this gap. Bracket
enables you to set up a tournament with as much flexibility as
possible, while still being easy to use.
</p>
{/*<Statistics />*/}
</div>
</div>
</div>
</section>
);
};

View File

@@ -0,0 +1,96 @@
import { Badge } from "./ui/badge";
import Image from "next/image";
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from "components/ui/card";
import image from "../content/img/assets/growth.png";
import image3 from "../content/img/assets/reflecting.png";
import image4 from "../content/img/assets/looking-ahead.png";
interface FeatureProps {
title: string;
description: string;
image: string;
}
const features: FeatureProps[] = [
{
title: "Responsive Design",
description:
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Nisi nesciunt est nostrum omnis ab sapiente.",
image: image.src,
},
{
title: "Intuitive user interface",
description:
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Nisi nesciunt est nostrum omnis ab sapiente.",
image: image3.src,
},
{
title: "AI-Powered insights",
description:
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Nisi nesciunt est nostrum omnis ab sapiente.",
image: image4.src,
},
];
const featureList: string[] = [
"Dark/Light theme",
"Reviews",
"Features",
"Pricing",
"Contact form",
"Our team",
"Responsive design",
"Newsletter",
"Minimalist",
];
export const AdvancedFeatures = () => {
return (
<section id="features" className="container py-16 space-y-8">
<h2 className="text-3xl lg:text-4xl font-bold md:text-center">
<span className="bg-linear-to-b from-primary/70 to-primary text-transparent bg-clip-text">
Advanced
</span>{" "}
Features
</h2>
<div className="flex flex-wrap md:justify-center gap-4">
{featureList.map((feature: string) => (
<div key={feature}>
<Badge variant="secondary" className="text-sm">
{feature}
</Badge>
</div>
))}
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{features.map(({ title, description, image }: FeatureProps) => (
<Card key={title}>
<CardHeader>
<CardTitle>{title}</CardTitle>
</CardHeader>
<CardContent>{description}</CardContent>
<CardFooter>
<Image
width={200}
height={200}
src={image}
alt="About feature"
className="w-[200px] lg:w-[300px] mx-auto"
/>
</CardFooter>
</Card>
))}
</div>
</section>
);
};

40
docs/components/Cta.tsx Normal file
View File

@@ -0,0 +1,40 @@
import { buttonVariants } from "./ui/button";
import Link from "next/link";
import { IconRocket } from "@tabler/icons-react";
export const Cta = () => {
return (
<section id="cta" className="bg-muted/50 py-16 my-24 sm:my-32">
<div className="container lg:grid lg:grid-cols-2 place-items-center">
<div className="lg:col-start-1">
<h2 className="text-3xl md:text-4xl font-bold ">
Take
<span className="bg-linear-to-b from-primary/70 to-primary text-transparent bg-clip-text">
{" "}
control{" "}
</span>
of your tournaments
</h2>
<p className="text-muted-foreground text-xl mt-4 mb-8 lg:mb-0">
Keep your tournament software in your own hands: no vendor lock-in,
no analytics data being collected, transparent & open-source
software.
</p>
</div>
<Link
href="https://www.bracketapp.nl/demo"
className={`w-full md:w-auto ${buttonVariants({
variant: "default",
})}`}
>
<IconRocket
size="32px"
style={{ marginRight: "0.5rem", height: "32px" }}
/>
Launch Demo
</Link>
</div>
</section>
);
};

81
docs/components/FAQ.tsx Normal file
View File

@@ -0,0 +1,81 @@
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "components/ui/accordion";
import Link from "next/link";
interface FAQProps {
question: string;
answer: string;
value: string;
}
const FAQList: FAQProps[] = [
{
question: "Is this template free?",
answer: "Yes. It is a free ChadcnUI template.",
value: "item-1",
},
{
question: "Lorem ipsum dolor sit amet consectetur adipisicing elit?",
answer:
"Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sint labore quidem quam? Consectetur sapiente iste rerum reiciendis animi nihil nostrum sit quo, modi quod.",
value: "item-2",
},
{
question:
"Lorem ipsum dolor sit amet Consectetur natus dolores minus quibusdam?",
answer:
"Lorem ipsum dolor sit amet consectetur, adipisicing elit. Labore qui nostrum reiciendis veritatis necessitatibus maxime quis ipsa vitae cumque quo?",
value: "item-3",
},
{
question: "Lorem ipsum dolor sit amet, consectetur adipisicing elit?",
answer: "Lorem ipsum dolor sit amet consectetur, adipisicing elit.",
value: "item-4",
},
{
question:
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Consectetur natus?",
answer:
"Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sint labore quidem quam? Consectetur sapiente iste rerum reiciendis animi nihil nostrum sit quo, modi quod.",
value: "item-5",
},
];
export const FAQ = () => {
return (
<section id="faq" className="container py-16">
<h2 className="text-3xl md:text-4xl font-bold mb-4">
Frequently Asked{" "}
<span className="bg-linear-to-b from-primary/70 to-primary text-transparent bg-clip-text">
Questions
</span>
</h2>
<Accordion type="single" collapsible className="w-full AccordionRoot">
{FAQList.map(({ question, answer, value }: FAQProps) => (
<AccordionItem key={value} value={value}>
<AccordionTrigger className="text-left">
{question}
</AccordionTrigger>
<AccordionContent>{answer}</AccordionContent>
</AccordionItem>
))}
</Accordion>
<h3 className="font-medium mt-4">
Still have questions?{" "}
<Link
href="https://github.com/evroon/bracket/discussions/new/choose"
className="text-primary transition-all border-border border-primary hover:border-b-2"
>
Open a discussion
</Link>
</h3>
</section>
);
};

View File

@@ -0,0 +1,75 @@
import { Card, CardDescription, CardHeader, CardTitle } from "./ui/card";
import builder from "../content/img/builder_preview.png";
import Image from "next/image";
import { ReactElement } from "react";
import { MdDashboard } from "react-icons/md";
import { IoBuildOutline } from "react-icons/io5";
import { RiDragDropLine } from "react-icons/ri";
interface ServiceProps {
title: string;
description: string;
icon: ReactElement;
}
const serviceList: ServiceProps[] = [
{
title: "Public Dashboard",
description: "Show the schedule and rankings to the public.",
icon: <MdDashboard size={48} />,
},
{
title: "Flexible Tournament Builder",
description:
"Add multiple swiss, single elimination and round-robin elements to the tournament.",
icon: <IoBuildOutline size={48} />,
},
{
title: "Drag & Drop Interface",
description:
"Drag-and-drop matches to different courts or reschedule them to another start time.",
icon: <RiDragDropLine size={48} />,
},
];
export const Features = () => {
return (
<section className="container py-16">
<div className="grid lg:grid-cols-[1fr_1fr] gap-8 place-items-center">
<div>
<h2 className="text-3xl md:text-4xl font-bold">Features</h2>
<p className="text-muted-foreground text-xl mt-4 mb-8 ">
Bracket is flexible, yet feature-rich.
</p>
<div className="flex flex-col gap-8">
{serviceList.map(({ icon, title, description }: ServiceProps) => (
<Card key={title}>
<CardHeader className="space-y-1 flex md:flex-row justify-start items-start gap-4">
<div className="mt-1 bg-primary/50 p-1 rounded-2xl">
{icon}
</div>
<div>
<CardTitle>{title}</CardTitle>
<CardDescription className="text-md mt-2">
{description}
</CardDescription>
</div>
</CardHeader>
</Card>
))}
</div>
</div>
<Image
width={200}
height={200}
src={builder.src}
className="w-[500px] md:w-[600px] lg:w-[700px] pt-15 lg:pt-25 object-contain"
alt="About services"
/>
</div>
</section>
);
};

143
docs/components/Footer.tsx Normal file
View File

@@ -0,0 +1,143 @@
import Link from "next/link";
import Image from "next/image";
import logo from "../content/img/logo.svg";
export const Footer = () => {
return (
<footer id="footer">
<hr className="w-11/12 mx-auto" />
<section className="container py-20 grid grid-cols-2 md:grid-cols-4 xl:grid-cols-6 gap-x-12 gap-y-8">
<div className="col-span-full xl:col-span-2">
<Link href="/" className="font-bold text-xl flex">
<Image
width={36}
height={36}
src={logo.src}
className="mr-2"
alt="Logo of Bracket"
/>
<b className="text-3xl">Bracket</b>
</Link>
</div>
<div className="flex flex-col gap-2">
<h3 className="font-bold text-lg">Intro</h3>
<div>
<Link href="/docs" className="opacity-60 hover:opacity-100">
Introduction
</Link>
</div>
<div>
<Link
href="/docs/running-bracket/quickstart"
className="opacity-60 hover:opacity-100"
>
Quickstart
</Link>
</div>
</div>
<div className="flex flex-col gap-2">
<h3 className="font-bold text-lg">Running Bracket</h3>
<div>
<Link
href="/docs/running-bracket/configuration"
className="opacity-60 hover:opacity-100"
>
Configuration
</Link>
</div>
<div>
<Link
href="/docs/deployment/deployment"
className="opacity-60 hover:opacity-100"
>
Deployment
</Link>
</div>
</div>
<div className="flex flex-col gap-2">
<h3 className="font-bold text-lg">About</h3>
<div>
<Link
href="/docs/usage/guide"
className="opacity-60 hover:opacity-100"
>
Usage
</Link>
</div>
<div>
<Link
href="/docs/running-bracket/faq"
className="opacity-60 hover:opacity-100"
>
FAQ
</Link>
</div>
<div>
<Link
href="https://github.com/evroon/bracket/blob/master/LICENSE"
className="opacity-60 hover:opacity-100"
>
License
</Link>
</div>
<div>
<Link
href="https://github.com/evroon/bracket/releases"
className="opacity-60 hover:opacity-100"
>
Releases
</Link>
</div>
</div>
<div className="flex flex-col gap-2">
<h3 className="font-bold text-lg">Community</h3>
<div>
<Link
href="https://github.com/evroon/bracket"
className="opacity-60 hover:opacity-100"
>
GitHub
</Link>
</div>
<div>
<Link
href="/docs/community/contributing/"
className="opacity-60 hover:opacity-100"
>
Contributing
</Link>
</div>
<div>
<Link
href="/docs/community/development/"
className="opacity-60 hover:opacity-100"
>
Development
</Link>
</div>
</div>
</section>
<section className="container pb-14 text-center">
<h3>
Bracket - Open-source Tournament System.
<br />
Licensed under AGPL-v3.0. Copyright © {new Date().getFullYear()}{" "}
Bracket. Built with Nextra.
</h3>
</section>
</footer>
);
};

View File

@@ -0,0 +1,24 @@
import { buttonVariants } from "./ui/button";
import Link from "next/link";
import { FaGithub } from "react-icons/fa";
export const GitHub = () => {
return (
<section className="container place-items-center py-20">
<section className="md:w-2/3">
<Link
href="https://github.com/evroon/bracket"
className={`w-full h-15 md:text-2xl border-2 ${buttonVariants({
variant: "outline",
})}`}
>
<FaGithub
size="32px"
style={{ marginRight: "1rem", height: "32px" }}
/>
Github Repository
</Link>
</section>
</section>
);
};

49
docs/components/Hero.tsx Normal file
View File

@@ -0,0 +1,49 @@
import { buttonVariants } from "./ui/button";
import Link from "next/link";
import { IconLibrary, IconRocket } from "@tabler/icons-react";
export const Hero = () => {
return (
<section className="container place-items-center py-20 md:py-24 gap-10">
<div className="text-center lg:text-start space-y-6">
<main className="text-5xl md:text-6xl font-bold">
<h1 className="inline">Free and open-source tournament management</h1>
</main>
<p className="text-xl text-muted-foreground md:w-10/12 mx-auto lg:mx-0">
Build tournament setups, add teams, schedule matches, keep track of
scores and present ranking live to the public.
</p>
<div className="space-y-4 md:space-y-0 md:space-x-4">
<Link
href="https://www.bracketapp.nl/demo"
className={`w-full md:w-1/3 h-12 ${buttonVariants({
variant: "default",
})}`}
>
<IconRocket
size="32px"
style={{ marginRight: "0.5rem", height: "32px" }}
/>
Launch Demo
</Link>
<Link
href="/docs"
className={`w-full md:w-1/3 h-12 ${buttonVariants({
variant: "outline",
})}`}
>
<IconLibrary
size="24px"
style={{ marginRight: "0.5rem", height: "32px" }}
/>
Read the docs
</Link>
</div>
</div>
<div className="shadow-sm"></div>
</section>
);
};

View File

@@ -0,0 +1,67 @@
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
import { ReactElement } from "react";
import { RiTeamFill } from "react-icons/ri";
import { PiTreeStructure } from "react-icons/pi";
import { BsCalendar4Week } from "react-icons/bs";
import { MdOutlineScoreboard } from "react-icons/md";
interface FeatureProps {
icon: ReactElement;
title: string;
description: string;
}
const features: FeatureProps[] = [
{
icon: <RiTeamFill fill={"#a581e9"} size={64} />,
title: "Add teams",
description:
"Register teams (and optionally players). You can upload a CSV file with all teams and players at once.",
},
{
icon: <PiTreeStructure fill={"#a581e9"} size={64} />,
title: "Choose format",
description:
"Add swiss, elimination or round-robing items to the tournament. Multiple stages are supported.",
},
{
icon: <BsCalendar4Week fill={"#a581e9"} size={64} />,
title: "Schedule matches",
description:
"Use the drag&drop interface to choose the courts and start times of the matches.",
},
{
icon: <MdOutlineScoreboard fill={"#a581e9"} size={64} />,
title: "Track scores & publish",
description:
"Enter the scores, customize the ranking and show it to the world on a dashboard.",
},
];
export const HowItWorks = () => {
return (
<section id="howItWorks" className="container text-center py-24 sm:py-32">
<h2 className="text-3xl md:text-4xl font-bold ">
How It{" "}
<span className="bg-linear-to-b from-primary/70 to-primary text-transparent bg-clip-text">
Works{" "}
</span>
</h2>
<p className="md:w-3/4 mx-auto mt-4 mb-8 text-xl text-muted-foreground"></p>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
{features.map(({ icon, title, description }: FeatureProps) => (
<Card key={title} className="bg-muted/50">
<CardHeader>
<CardTitle className="grid gap-4 place-items-center">
{icon}
{title}
</CardTitle>
</CardHeader>
<CardContent>{description}</CardContent>
</Card>
))}
</div>
</section>
);
};

View File

@@ -0,0 +1,15 @@
import Image from "next/image";
import preview from "../content/img/bracket-screenshot-design.png";
export const PreviewImage = () => {
return (
<section className="container place-items-center py-20">
<Image
alt="Design of the Bracket dashboard"
src={preview.src}
width={1000}
height={1000}
/>
</section>
);
};

View File

@@ -0,0 +1,38 @@
export const Statistics = () => {
interface statsProps {
quantity: string;
description: string;
}
const stats: statsProps[] = [
{
quantity: "2.7K+",
description: "Users",
},
{
quantity: "1.8K+",
description: "Subscribers",
},
{
quantity: "112",
description: "Downloads",
},
{
quantity: "4",
description: "Products",
},
];
return (
<section id="statistics">
<div className="grid grid-cols-2 lg:grid-cols-4 gap-8">
{stats.map(({ quantity, description }: statsProps) => (
<div key={description} className="space-y-2 text-center">
<h2 className="text-3xl sm:text-4xl font-bold ">{quantity}</h2>
<p className="text-xl text-muted-foreground">{description}</p>
</div>
))}
</div>
</section>
);
};

View File

@@ -0,0 +1,60 @@
import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDown } from "lucide-react";
import { cn } from "lib/utils";
const Accordion = AccordionPrimitive.Root;
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-border border-b", className)}
{...props}
/>
));
AccordionItem.displayName = "AccordionItem";
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
className,
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
));
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div
className={cn("pb-4 pt-0 text-muted-foreground text-[16px]", className)}
>
{children}
</div>
</AccordionPrimitive.Content>
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };

View File

@@ -0,0 +1,36 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "lib/utils";
const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
},
);
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
);
}
export { Badge, badgeVariants };

View File

@@ -0,0 +1,56 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
},
);
Button.displayName = "Button";
export { Button, buttonVariants };

View File

@@ -0,0 +1,86 @@
import * as React from "react";
import { cn } from "lib/utils";
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border border-border bg-card text-card-foreground shadow-xs",
className,
)}
{...props}
/>
));
Card.displayName = "Card";
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
));
CardHeader.displayName = "CardHeader";
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className,
)}
{...props}
/>
));
CardTitle.displayName = "CardTitle";
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
));
CardDescription.displayName = "CardDescription";
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
));
CardContent.displayName = "CardContent";
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
));
CardFooter.displayName = "CardFooter";
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
};

View File

@@ -0,0 +1,138 @@
import * as React from "react";
import * as SheetPrimitive from "@radix-ui/react-dialog";
import { cva, type VariantProps } from "class-variance-authority";
import { X } from "lucide-react";
import { cn } from "lib/utils";
const Sheet = SheetPrimitive.Root;
const SheetTrigger = SheetPrimitive.Trigger;
const SheetClose = SheetPrimitive.Close;
const SheetPortal = SheetPrimitive.Portal;
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-background/80 backdrop-blur-xs data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className,
)}
{...props}
ref={ref}
/>
));
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
{
variants: {
side: {
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom:
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
},
},
defaultVariants: {
side: "right",
},
},
);
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = "right", className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ side }), className)}
{...props}
>
{children}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
));
SheetContent.displayName = SheetPrimitive.Content.displayName;
const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className,
)}
{...props}
/>
);
SheetHeader.displayName = "SheetHeader";
const SheetFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className,
)}
{...props}
/>
);
SheetFooter.displayName = "SheetFooter";
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold text-foreground", className)}
{...props}
/>
));
SheetTitle.displayName = SheetPrimitive.Title.displayName;
const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
));
SheetDescription.displayName = SheetPrimitive.Description.displayName;
export {
Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
};

View File

@@ -1,6 +1,7 @@
---
sidebar_position: 5
title: API
---
# API
Bracket has a REST API powered by FastAPI. The frontend sends requests to this API to the backend.

View File

@@ -0,0 +1,115 @@
---
title: Contributing
---
# Contributing
If you're using Bracket and would like to help support its development, that would be greatly
appreciated!
Several areas that we need a bit of help with at the moment are:
- ⭐ **Star Bracket** on GitHub
- 🌐 **Translating**: Help make Bracket available to non-native English speakers by adding your
language. See [Translating](#translating) below.
- 📣 **Spread the word** by sharing Bracket to help new users discover it
- 🖥️ **Submit a PR** to add a new feature, fix a bug, extend/update the docs or something else
## Translating
### Adding translations (via crowdin)
Bracket uses [crowdin](https://crowdin.com/project/bracket) for translations. You can add/improve
translations here in your language.
If you want to add a new language, please create an issue and I will add the language to Crowdin.
### Manually adding translations
You can add a translation by copying the English `en` locale
([here](https://github.com/evroon/bracket/tree/master/frontend/public/locales)) directory.
Rename the directory to the name of your locale, and start translating the `common.json` file inside
the directory. It might be useful to use an online tool (Google `translate json file`) to do the
translation for you, and then carefully check and correct any mistakes.
## Contributors
{/*<!-- markdownlint-disable line-length -->*/}
{/*<!-- readme: collaborators,contributors,dependabot/- -start -->*/}
<table>
<tbody>
<tr>
<td align="center">
<a href="https://github.com/evroon">
<img src="https://avatars.githubusercontent.com/u/11857441?v=4" width="100;" alt="evroon"/>
<br />
<sub><b>Erik Vroon</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/robigan">
<img src="https://avatars.githubusercontent.com/u/35210888?v=4" width="100;" alt="robigan"/>
<br />
<sub><b>Null</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/BachErik">
<img src="https://avatars.githubusercontent.com/u/75324423?v=4" width="100;" alt="BachErik"/>
<br />
<sub><b>BachErik</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/djpiper28">
<img src="https://avatars.githubusercontent.com/u/13609136?v=4" width="100;" alt="djpiper28"/>
<br />
<sub><b>Danny Piper</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Sevichecc">
<img src="https://avatars.githubusercontent.com/u/91365763?v=4" width="100;" alt="Sevichecc"/>
<br />
<sub><b>SevicheCC</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/IzStriker">
<img src="https://avatars.githubusercontent.com/u/44909896?v=4" width="100;" alt="IzStriker"/>
<br />
<sub><b>IzStriker</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/babeuh">
<img src="https://avatars.githubusercontent.com/u/60193302?v=4" width="100;" alt="babeuh"/>
<br />
<sub><b>Raphael Le Goaller</b></sub>
</a>
</td>
</tr>
</tbody>
</table>
{/*<!-- readme: collaborators,contributors,dependabot/- -end -->*/}
## Star History
<a href="https://star-history.com/#evroon/bracket&Date">
<picture>
<source
media="(prefers-color-scheme: dark)"
srcSet="https://api.star-history.com/svg?repos=evroon/bracket&type=Date&theme=dark"
/>
<source
media="(prefers-color-scheme: light)"
srcSet="https://api.star-history.com/svg?repos=evroon/bracket&type=Date"
/>
<img
alt="Star History Chart"
src="https://api.star-history.com/svg?repos=evroon/bracket&type=Date"
/>
</picture>
</a>
{/*<!-- markdownlint-enable line-length -->*/}

View File

@@ -1,12 +1,12 @@
---
sidebar_position: 2
title: Development
---
# Developing
# Development
This guide explains how to run Bracket without Docker. They cover database setup, configuration and
how to run the frontend and backend. If you quickly want to get up and running, please read
[quickstart.md](../running-bracket/quickstart.md).
[quickstart](../running-bracket/quickstart.mdx).
## Database
@@ -31,7 +31,7 @@ You can do the same but replace the user and database name with:
- `bracket_prod`: for a production database
The database URL can be specified per environment in the `.env` files (see
[config](../running-bracket/configuration.md)).
[config](../running-bracket/configuration.mdx)).
## Running the frontend and backend
@@ -78,7 +78,7 @@ Just install it according to the [docs](https://f1bonacc1.github.io/process-comp
and then run:
```shell
cp process-compose-example.yml process-compose.yml
cp process-compose-example.yml process-compose.yml
process-compose up -d
```

View File

@@ -1,3 +1,6 @@
---
title: Cloud services
---
# Cloud services
## Vercel

View File

@@ -1,3 +1,8 @@
---
title: Docker
---
import { Callout } from 'nextra/components'
# Docker
This section describes how to deploy Bracket (frontend and backend) to docker using docker-compose.
@@ -21,10 +26,8 @@ services:
- "3000:3000"
environment:
NODE_ENV: "production"
// highlight-start
NEXT_PUBLIC_API_BASE_URL: "http://your-site.com:8400"
NEXT_PUBLIC_HCAPTCHA_SITE_KEY: "10000000-ffff-ffff-ffff-000000000001"
// highlight-end
restart: unless-stopped
bracket-backend:
@@ -34,13 +37,10 @@ services:
- "8400:8400"
environment:
ENVIRONMENT: "PRODUCTION"
// highlight-start
PG_DSN: "postgresql://bracket_prod:bracket_prod@postgres:5432/bracket_prod"
CORS_ORIGINS: https://your-site.com
JWT_SECRET: change_me
// highlight-end
volumes:
// highlight-next-line
- ./backend/static:/app/static
restart: unless-stopped
depends_on:
@@ -54,7 +54,6 @@ services:
POSTGRES_USER: bracket_prod
POSTGRES_PASSWORD: bracket_prod
volumes:
// highlight-next-line
- ./postgres:/var/lib/postgresql/data
```
@@ -77,13 +76,11 @@ Replace the following values for `bracket-backend`:
can only come from your actual frontend
- `JWT_SECRET`: Generate a secret to create JWTs using `openssl rand -hex 32`
:::warning
Note that your `docker-compose.yml` file now contains secrets.
If you want a more secure setup, you can store secrets in separate files on the host and
load them via [docker secrets](https://docs.docker.com/compose/use-secrets/).
:::
<Callout>
Note that your `docker-compose.yml` file now contains secrets.
If you want a more secure setup, you can store secrets in separate files on the host and
load them via [docker secrets](https://docs.docker.com/compose/use-secrets/).
</Callout>
## 4. Update volume bindings

View File

@@ -1,11 +1,11 @@
---
sidebar_position: 0
title: Deployment
---
# Deployment
The guides in this directory explain how to run Bracket in production. If you quickly want to get up
and running, please read [quickstart.md](../running-bracket/quickstart.md).
and running, please read [quickstart.md](../running-bracket/quickstart.mdx).
## Configuration
@@ -25,7 +25,7 @@ Optional:
- `SENTRY_DSN`: The [Sentry](https://sentry.io) DSN for monitoring and error tracking
- `BASE_URL`: The base url of the API used for SSO
See [the config docs](../running-bracket/configuration.md) for more information.
See [the config docs](../running-bracket/configuration.mdx) for more information.
### Frontend configuration

View File

@@ -1,10 +1,15 @@
---
title: Nomad
---
# Nomad
This section describes how to deploy Bracket (frontend and backend) to
[Nomad](https://www.nomadproject.io).
First, make sure you have a running Nomad cluster. See the
[production deployment guide](https://developer.hashicorp.com/nomad/tutorials/enterprise/production-deployment-guide-vm-with-consul) on how to achieve that. <!-- markdownlint-disable-line line-length no-inline-html -->
[production deployment guide](https://developer.hashicorp.com/nomad/tutorials/enterprise/production-deployment-guide-vm-with-consul) {/*<!-- markdownlint-disable-line -->*/}
on how to achieve that.
Then, you can use the following files describing the tasks for the backend and frontend.
@@ -30,7 +35,7 @@ job "bracket-backend" {
task "api" {
driver = "docker"
env {
ENVIRONMENT = "PRODUCTION"
PG_DSN = "postgresql://bracket_prod:bracket_prod@postgres:5432/bracket_prod"
@@ -82,7 +87,7 @@ job "bracket-frontend" {
task "api" {
driver = "docker"
env {
NEXT_PUBLIC_API_BASE_URL = "https://my.bracketdomain.com"
NEXT_PUBLIC_HCAPTCHA_SITE_KEY = "xxxxx"

View File

@@ -1,3 +1,7 @@
---
title: Systemd
---
# Systemd
This section describes how to deploy Bracket (frontend and backend) as a Systemd service on Linux.

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 124 KiB

View File

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View File

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View File

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

View File

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

View File

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 125 KiB

View File

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 155 KiB

View File

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

View File

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

View File

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@@ -1,7 +1,6 @@
---
sidebar_position: 1
title: Introduction
---
# Introduction
[Bracket](https://github.com/evroon/bracket) is a tournament system meant to be easy to use. Bracket
@@ -43,6 +42,6 @@ experience to a project with a real purpose.
## Quickstart
To get started in selfhosting Bracket, follow the steps described
in [quickstart](running-bracket/quickstart.md).
in [quickstart](/docs/running-bracket/quickstart.mdx).
To learn how to organize a tournament in Bracket, read the [usage guide](usage/guide.md).
To learn how to organize a tournament in Bracket, read the [usage guide](/docs/usage/guide.mdx).

View File

@@ -1,7 +1,8 @@
---
sidebar_position: 1
title: Configuration
---
# Configuration
## Backend

View File

@@ -1,7 +1,8 @@
---
sidebar_position: 2
title: FAQ
---
# FAQ
## I ran Bracket with the default `docker-compose.yml` but I can't connect to the backend?
@@ -13,4 +14,4 @@ This is likely because you are trying to access Bracket on a different address t
- You will also need to update `CORS_ORIGINS` to the address of the frontend, e.g.
`https://app.example.org`.
Please consult [configuration docs](configuration.md) for more information.
Please consult [configuration docs](configuration.mdx) for more information.

View File

@@ -1,7 +1,8 @@
---
sidebar_position: 0
title: Quickstart
---
# Quickstart
To quickly run bracket to see how it works, clone it and run `docker compose up`:

View File

@@ -1,12 +1,18 @@
---
title: Usage guide
---
import {Callout} from 'nextra/components'
# Usage guide
Setting up a tournament in a tournament management system like Bracket typically involves several
key steps. Below is a general guide on how to set up a tournament step-by-step.
:::info
<Callout type="info">
This guide assumes you are logged in to Bracket. This guide also assumes you use the demo of Bracket
(`https://www.bracketapp.nl`). If you are selfhosting Bracket, just use your own domain instead.
:::
</Callout>
## 1. Create a New Tournament
@@ -42,13 +48,13 @@ pages responsible for different aspects of the tournament (courts, teams etc.).
You need to add the teams (and optionally players) who will participate in the tournament.
:::tip
<Callout type="info">
Adding players is optional and for display purposes only.
Players are part of teams and therefore it can be useful to add players in the system to know which
player plays in which team. In case you are organising a tournament where every person plays for
themselves, create teams with the names of these persons and don't add any players to those teams.
:::
</Callout>
### Adding Players

View File

@@ -1,3 +1,7 @@
---
title: Terminology
---
# Terminology
Here is a list of commonly used terms and their meanings:

View File

@@ -1,7 +0,0 @@
{
"label": "Community",
"position": 4,
"link": {
"type": "generated-index"
}
}

View File

@@ -1,105 +0,0 @@
---
sidebar_position: 1
---
# Contributing
If you're using Bracket and would like to help support its development, that would be greatly
appreciated!
Several areas that we need a bit of help with at the moment are:
-**Star Bracket** on GitHub
- 🌐 **Translating**: Help make Bracket available to non-native English speakers by adding your
language. See [Translating](#translating) below.
- 📣 **Spread the word** by sharing Bracket to help new users discover it
- 🖥️ **Submit a PR** to add a new feature, fix a bug, extend/update the docs or something else
## Translating
### Adding translations (via crowdin)
Bracket uses [crowdin](https://crowdin.com/project/bracket) for translations. You can add/improve
translations here in your language.
If you want to add a new language, please create an issue and I will add the language to Crowdin.
### Manually adding translations
You can add a translation by copying the English `en` locale
([here](https://github.com/evroon/bracket/tree/master/frontend/public/locales)) directory.
Rename the directory to the name of your locale, and start translating the `common.json` file inside
the directory. It might be useful to use an online tool (Google `translate json file`) to do the
translation for you, and then carefully check and correct any mistakes.
## Contributors
<!-- markdownlint-disable line-length no-inline-html -->
<!-- readme: collaborators,contributors,dependabot/- -start -->
<table>
<tr>
<td align="center">
<a href="https://github.com/evroon">
<img src="https://avatars.githubusercontent.com/u/11857441?v=4" width="100;" alt="evroon"/>
<br />
<sub><b>Erik Vroon</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/robigan">
<img src="https://avatars.githubusercontent.com/u/35210888?v=4" width="100;" alt="robigan"/>
<br />
<sub><b>Null</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/BachErik">
<img src="https://avatars.githubusercontent.com/u/75324423?v=4" width="100;" alt="BachErik"/>
<br />
<sub><b>BachErik</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/djpiper28">
<img src="https://avatars.githubusercontent.com/u/13609136?v=4" width="100;" alt="djpiper28"/>
<br />
<sub><b>Danny Piper</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Sevichecc">
<img src="https://avatars.githubusercontent.com/u/91365763?v=4" width="100;" alt="Sevichecc"/>
<br />
<sub><b>SevicheCC</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/IzStriker">
<img src="https://avatars.githubusercontent.com/u/44909896?v=4" width="100;" alt="IzStriker"/>
<br />
<sub><b>IzStriker</b></sub>
</a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/babeuh">
<img src="https://avatars.githubusercontent.com/u/60193302?v=4" width="100;" alt="babeuh"/>
<br />
<sub><b>Raphael Le Goaller</b></sub>
</a>
</td></tr>
</table>
<!-- readme: collaborators,contributors,dependabot/- -end -->
## Star History
[![stargazers](https://img.shields.io/github/stars/evroon/bracket)](https://github.com/evroon/bracket/stargazers)
<a href="https://star-history.com/#evroon/bracket&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=evroon/bracket&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=evroon/bracket&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=evroon/bracket&type=Date" />
</picture>
</a>
<!-- markdownlint-enable line-length no-inline-html -->

View File

@@ -1,7 +0,0 @@
{
"label": "Deployment",
"position": 3,
"link": {
"type": "generated-index"
}
}

View File

@@ -1,7 +0,0 @@
{
"label": "Running Bracket",
"position": 2,
"link": {
"type": "generated-index"
}
}

View File

@@ -1,7 +0,0 @@
{
"label": "Usage",
"position": 4,
"link": {
"type": "generated-index"
}
}

View File

@@ -1,173 +0,0 @@
// @ts-check
// Note: type annotations allow type checking and IDEs autocompletion
import { themes as prismThemes } from "prism-react-renderer";
const { themes } = require("prism-react-renderer");
/** @type {import('@docusaurus/types').Config} */
const config = {
title: "Bracket - Free & open source tournament management",
tagline: `Bracket is a free and open source tournament system. Set up a tournament, add teams, schedule matches, track scores and present live rankings.`,
favicon: "img/logo.svg",
url: "https://docs.bracketapp.nl",
baseUrl: "/",
organizationName: "evroon",
projectName: "bracket",
onBrokenLinks: "throw",
onBrokenMarkdownLinks: "warn",
trailingSlash: true,
// Even if you don't use internalization, you can use this field to set useful
// metadata like html lang. For example, if your site is Chinese, you may want
// to replace "en" with "zh-Hans".
i18n: {
defaultLocale: "en",
locales: ["en"],
},
scripts: [
{
src: "https://analytics.bracketapp.nl/script.js",
async: true,
"data-website-id": "9c5b1839-5cbd-4d04-b95b-a217838898a9",
"data-domains": "docs.bracketapp.nl",
},
],
presets: [
[
"classic",
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
sidebarPath: require.resolve("./sidebars.js"),
editUrl: "https://github.com/evroon/bracket/tree/master/docs/",
},
blog: {
showReadingTime: true,
editUrl: "https://github.com/evroon/bracket/tree/master/docs/",
},
theme: {
customCss: require.resolve("./src/css/custom.css"),
},
}),
],
],
plugins: [require.resolve("docusaurus-lunr-search")],
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
// Replace with your project's social card
image: "img/bracket-screenshot-design.png",
navbar: {
title: "Bracket",
logo: {
alt: "Bracket Logo",
src: "img/logo.svg",
},
items: [
{
type: "docSidebar",
sidebarId: "tutorialSidebar",
position: "left",
label: "Documentation",
},
{
label: "Quickstart",
href: "/docs/running-bracket/quickstart",
position: "left",
},
{
href: "https://github.com/evroon/bracket",
label: "GitHub",
position: "left",
},
],
},
colorMode: {
defaultMode: "dark",
respectPrefersColorScheme: false,
disableSwitch: true,
},
footer: {
style: "dark",
links: [
{
title: "Intro",
items: [
{
label: "Introduction",
to: "/docs/intro",
},
{
label: "Quickstart",
to: "/docs/running-bracket/quickstart",
},
],
},
{
title: "Running Bracket",
items: [
{
label: "Configuration",
to: "/docs/running-bracket/configuration",
},
{
label: "Deployment",
to: "/docs/deployment",
},
],
},
{
title: "Community",
items: [
{
label: "Contributing",
to: "/docs/community/contributing",
},
{
label: "Developing",
to: "/docs/community/development",
},
],
},
{
title: "More",
items: [
{
label: "GitHub",
href: "https://github.com/evroon/bracket",
},
{
label: "License",
href: "https://github.com/evroon/bracket/blob/master/LICENSE",
},
{
label: "Changelog",
href: "https://github.com/evroon/bracket/releases",
},
],
},
],
copyright: `Bracket - Self-Hosted Tournament System.<br/> Licensed under AGPL-v3.0. Copyright © ${new Date().getFullYear()} Bracket. Built with Docusaurus.`,
},
prism: {
theme: prismThemes.oneLight,
darkTheme: prismThemes.oneDark,
additionalLanguages: [
"bash",
"diff",
"json",
"systemd",
"docker",
"toml",
"hcl",
"yaml",
],
},
}),
};
module.exports = config;

6
docs/lib/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

13
docs/mdx-components.tsx Normal file
View File

@@ -0,0 +1,13 @@
import { useMDXComponents as getThemeComponents } from "nextra-theme-docs"; // nextra-theme-blog or your custom theme
// Get the default MDX components
const themeComponents = getThemeComponents();
// Merge components
// @ts-expect-error 1231231
export function useMDXComponents(components) {
return {
...themeComponents,
...components,
};
}

15
docs/next.config.mjs Normal file
View File

@@ -0,0 +1,15 @@
import nextra from 'nextra'
const withNextra = nextra({
contentDirBasePath: '/docs'
})
export default withNextra({
reactStrictMode: true,
skipTrailingSlashRedirect: true,
// Export only when building in GitHub Actions
output: process.env.GITHUB_ACTION ? 'export' : 'export',
images: {
unoptimized: true
}
})

View File

@@ -1,88 +1,55 @@
{
"name": "docs",
"name": "bracket-docs",
"version": "0.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"build": "next build",
"dev": "next dev",
"start": "next start",
"prettier:check": "prettier --check \"**/*.{js,jsx,ts,tsx}\"",
"prettier:write": "prettier --write \"**/*.{js,jsx,ts,tsx}\"",
"test": "yarn run prettier:write && yarn run markdownlint-cli2 --fix",
"test-check": "yarn run prettier:check && yarn run markdownlint-cli2 --config .markdownlint-cli2.mjs",
"lint:markdown": "markdownlint-cli2"
"lint:markdown": "markdownlint-cli2",
"postbuild": "pagefind --site .next/server/app --output-path out/_pagefind"
},
"dependencies": {
"@docusaurus/core": "^3.0.1",
"@docusaurus/preset-classic": "^3.0.1",
"@emotion/react": "^11.11.1",
"@emotion/server": "^11.11.0",
"@mantine/carousel": "7.2.2",
"@mantine/core": "7.2.2",
"@mantine/hooks": "7.2.2",
"@mantine/spotlight": "7.2.2",
"@mantinex/dev-icons": "^1.0.2",
"@mdx-js/react": "^3.0.0",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-navigation-menu": "^1.2.0",
"@radix-ui/react-slot": "^1.1.0",
"@tabler/icons-react": "^3.19.0",
"clsx": "^2.0.0",
"docusaurus-lunr-search": "^3.3.0",
"embla-carousel-react": "^8.0.0-rc15",
"prism-react-renderer": "^2.3.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"yarn-upgrade-all": "^0.7.2"
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"lucide-react": "^0.424.0",
"next": "^15.2.0",
"nextra": "^4.2.13",
"nextra-theme-docs": "^4.2.13",
"pagefind": "^1.3.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-icons": "^5.5.0",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@babel/core": "^7.23.5",
"@docusaurus/module-type-aliases": "^3.0.1",
"@next/eslint-plugin-next": "^15.0.0",
"@tailwindcss/postcss": "^4.0.9",
"@trivago/prettier-plugin-sort-imports": "^5.0.1",
"@types/jest": "^29.5.11",
"@types/node": "^22.7.5",
"@types/react": "^18.2.42",
"@typescript-eslint/eslint-plugin": "^6.13.2",
"@typescript-eslint/parser": "^8.10.0",
"babel-loader": "^10.0.0",
"eslint": "^9.13.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.1.0",
"eslint-config-mantine": "^3.1.0",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-jest": "^28.8.3",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-testing-library": "^7.0.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"@types/mdx": "^2.0.13",
"@types/node": "22.13.4",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"autoprefixer": "^10.4.19",
"eslint": "^8",
"eslint-config-next": "15.1.7",
"markdownlint-cli2": "^0.14.0",
"markdownlint-rule-relative-links": "^4.0.1",
"postcss": "^8.4.32",
"postcss-preset-mantine": "^1.11.1",
"postcss-simple-vars": "^7.0.1",
"postcss": "^8.5.3",
"prettier": "^3.1.0",
"ts-jest": "^29.1.1",
"typescript": "^5.3.3"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"engines": {
"node": ">=18.0"
"tailwindcss": "^4.0.9",
"typescript": "^5.7.3"
}
}

View File

@@ -1,14 +0,0 @@
module.exports = {
plugins: {
'postcss-preset-mantine': {},
'postcss-simple-vars': {
variables: {
'mantine-breakpoint-xs': '36em',
'mantine-breakpoint-sm': '48em',
'mantine-breakpoint-md': '62em',
'mantine-breakpoint-lg': '75em',
'mantine-breakpoint-xl': '88em',
},
},
},
};

6
docs/postcss.config.mjs Normal file
View File

@@ -0,0 +1,6 @@
export default {
plugins: {
'@tailwindcss/postcss': {},
autoprefixer: {},
},
};

View File

@@ -1,33 +0,0 @@
/**
* Creating a sidebar enables you to:
- create an ordered group of docs
- render a sidebar for each doc of that group
- provide next/previous navigation
The sidebars can be generated from the filesystem, or explicitly defined here.
Create as many sidebars as you want.
*/
// @ts-check
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
// By default, Docusaurus generates a sidebar from the docs folder structure
tutorialSidebar: [{ type: "autogenerated", dirName: "." }],
// But you can create a sidebar manually
/*
tutorialSidebar: [
'intro',
'hello',
{
type: 'category',
label: 'Tutorial',
items: ['tutorial-basics/create-a-document'],
},
],
*/
};
module.exports = sidebars;

View File

@@ -1,60 +0,0 @@
import { Button, Center, Container, Group, Text } from "@mantine/core";
import classes from "./styles.module.css";
import React from "react";
import { IconLibrary, IconRocket } from "@tabler/icons-react";
export function HeroTitle() {
return (
<div className={classes.wrapper}>
<Container maxSize={"400px"} className={classes.inner}>
<h1 className={classes.title}>
Free and open source{" "}
<Text
component="span"
variant="gradient"
gradient={{ from: "indigo", to: "#674ad6" }}
inherit
>
tournament scheduling
</Text>{" "}
system
</h1>
<Text className={classes.description} color="dimmed">
Build tournament setups, add teams, schedule matches, keep track of
scores and present ranking live to the public.
</Text>
<Group className={classes.controls}>
<Button
size="xl"
className={classes.control}
variant="gradient"
gradient={{ from: "indigo", to: "#674ad6" }}
onClick={() => {
open("https://www.bracketapp.nl/demo", "_self");
}}
>
<Center inline>
<IconRocket size="32px" style={{ marginRight: "0.5rem" }} />
Demo
</Center>
</Button>
<Button
size="xl"
className={classes.control}
variant="default"
onClick={() => {
open("docs/running-bracket/quickstart", "_self");
}}
>
<Center inline>
<IconLibrary size="32px" style={{ marginRight: "0.5rem" }} />
Get started
</Center>
</Button>
</Group>
</Container>
</div>
);
}

View File

@@ -1,61 +0,0 @@
.wrapper {
position: relative;
box-sizing: border-box;
background-color: var(--mantine-color-dark-8);
}
.inner {
position: relative;
padding-top: rem(200px);
padding-bottom: rem(120px);
@media (max-width: $mantine-breakpoint-sm) {
padding-bottom: rem(80px);
padding-top: rem(80px);
}
}
.title {
font-family: Greycliff CF, var(--mantine-font-family);
font-size: rem(62px);
font-weight: 900;
line-height: 1.1;
margin: 0;
padding: 0;
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
@media (max-width: $mantine-breakpoint-sm) {
font-size: rem(42px);
line-height: 1.2;
}
}
.description {
margin-top: var(--mantine-spacing-xl);
font-size: rem(24px);
@media (max-width: $mantine-breakpoint-sm) {
font-size: rem(18px);
}
}
.controls {
margin-top: calc(var(--mantine-spacing-xl) * 2);
@media (max-width: $mantine-breakpoint-sm) {
margin-top: var(--mantine-spacing-xl);
}
}
.control {
height: rem(54px);
padding-left: rem(38px);
padding-right: rem(38px);
@media (max-width: $mantine-breakpoint-sm) {
height: rem(54px);
padding-left: rem(18px);
padding-right: rem(18px);
flex: 1;
}
}

View File

@@ -1,49 +0,0 @@
import { Container, Image } from "@mantine/core";
import React from "react";
import { Carousel } from "@mantine/carousel";
export function HomeCarousel() {
return (
<Container width={"100%"} mb="md">
<Carousel
withIndicators
slideSize={"100%"}
slideGap="md"
loop
align="center"
slidesToScroll={1}
>
<Carousel.Slide>
<Image
alt="preview image of the tournament builder page in Bracket"
src={require("@site/static/img/builder_preview.png").default}
/>
</Carousel.Slide>
<Carousel.Slide>
<Image
alt="preview image of the tournament planning page in Bracket"
src={require("@site/static/img/planning_preview.png").default}
/>
</Carousel.Slide>
<Carousel.Slide>
<Image
alt="preview image of the tournament scheduling page in Bracket"
src={require("@site/static/img/schedule_preview.png").default}
/>
</Carousel.Slide>
<Carousel.Slide>
<Image
alt="preview image of the courts page in Bracket"
src={require("@site/static/img/courts_preview.png").default}
/>
</Carousel.Slide>
<Carousel.Slide>
<Image
alt="preview image of the standings page in Bracket"
src={require("@site/static/img/standings_preview.png").default}
/>
</Carousel.Slide>
</Carousel>
</Container>
);
}

View File

@@ -1,2 +0,0 @@
.wrapper {
}

View File

@@ -1,70 +0,0 @@
import { Card, Container, rem, SimpleGrid, Text } from "@mantine/core";
import {
IconBrandOpenSource,
IconCloud,
IconTool,
IconUser,
} from "@tabler/icons-react";
import classes from "../pages/index.module.css";
import React from "react";
const cardData = [
{
title: "Open-source and free",
description:
"Bracket is fully open source and free to use, licensed under the AGPL-3.0 license.",
icon: IconBrandOpenSource,
},
{
title: "Flexible",
description:
"Bracket supports the standard tournament types, teams can be added/changed\n" +
" during the tournament and new matches can be scheduled dynamically.",
icon: IconTool,
},
{
title: "Easy to use",
description:
"The UI is meant to be easy to use while providing maximum flexibility.",
icon: IconUser,
},
{
title: "Self-hosted",
description:
"You are free to host it yourself. Setup is easy; either run it in Docker or run it the\n" +
" natively on the host. The only external dependency is a PostgreSQL database.",
icon: IconCloud,
},
];
export default function FeaturesCards() {
const features = cardData.map((feature) => (
<Card
key={feature.title}
shadow="md"
radius="md"
className={classes.card}
padding="xl"
>
<feature.icon
style={{ width: rem(50), height: rem(50) }}
stroke={2}
color={"#674ad6"}
/>
<Text fz="lg" fw={500} className={classes.cardTitle} mt="md">
{feature.title}
</Text>
<Text fz="sm" c="dimmed" mt="sm">
{feature.description}
</Text>
</Card>
));
return (
<Container size="lg" py="xl">
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="xl" mt={0}>
{features}
</SimpleGrid>
</Container>
);
}

View File

@@ -1,36 +0,0 @@
/**
* Any CSS included here will be global. The classic template
* bundles Infima by default. Infima is a CSS framework designed to
* work well for content-centric websites.
*
* Use https://docusaurus.io/docs/styling-layout to change these values.
*/
/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #2e8555;
--ifm-color-primary-dark: #29784c;
--ifm-color-primary-darker: #277148;
--ifm-color-primary-darkest: #205d3b;
--ifm-color-primary-light: #33925d;
--ifm-color-primary-lighter: #359962;
--ifm-color-primary-lightest: #3cad6e;
--ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
}
/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme='dark'] {
--ifm-color-primary: #bcafed;
--ifm-color-primary-dark: #a08de5;
--ifm-color-primary-darker: #927de2;
--ifm-color-primary-darkest: #674ad6;
--ifm-color-primary-light: #d8d1f5;
--ifm-color-primary-lighter: #e6e1f8;
--ifm-color-primary-lightest: #ffffff;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
}
.footer {
background-color: light-dark(var(--mantine-color-dark-8), var(--mantine-color-dark-8));
}

View File

@@ -1,100 +0,0 @@
import React from "react";
import Layout from "@theme/Layout";
import {
Center,
Container,
createTheme,
Image,
MantineProvider,
Paper,
rem,
Text,
ThemeIcon,
Title,
} from "@mantine/core";
import "@mantine/core/styles.css";
import "@mantine/carousel/styles.css";
import { HeroTitle } from "../components/HeroTitle";
import { HomeCarousel } from "../components/HomeCarousel";
import classes from "./index.module.css";
import FeaturesCards from "../components/feature_cards";
import { IconBrandGithub } from "@tabler/icons-react";
const theme = createTheme({});
function CardGradient() {
return (
<Center mx="1rem" mt="2rem">
<Paper
radius="md"
className={classes.social_card}
onClick={() => {
open("https://github.com/evroon/bracket");
}}
>
<Center inline>
<ThemeIcon
size="xl"
radius="md"
variant="filled"
color="black"
mr="1rem"
>
<IconBrandGithub
style={{ width: rem(38), height: rem(38) }}
stroke={1.5}
/>
</ThemeIcon>
<Text size="xl" fw={500} inline>
GitHub
</Text>
</Center>
<Text size="sm" mt="sm" c="dimmed">
Go to the GitHub repository to star or fork Bracket, create issues/PRs
or start discussions.
</Text>
</Paper>
</Center>
);
}
export default function Home() {
return (
<MantineProvider theme={theme} defaultColorScheme="dark">
<Layout
title={""}
description="Bracket is a free and open source tournament system. Set up a tournament, add teams, schedule matches, track scores and present live rankings."
>
<HeroTitle />
<main>
<Center>
<Container mt="lg" px="0px" mx="1rem">
<Image
alt="Design of the Bracket dashboard"
src={
require("@site/static/img/bracket-screenshot-design.png")
.default
}
/>
</Container>
</Center>
<CardGradient />
<Title order={2} className={classes.title} ta="center" mt="lg">
Features
</Title>
<FeaturesCards />
<Container mt="lg" px="0px">
<Title order={2} className={classes.title} ta="center" my="lg">
Screenshots
</Title>
<HomeCarousel />
</Container>
</main>
</Layout>
</MantineProvider>
);
}

View File

@@ -1,71 +0,0 @@
/**
* CSS files with the .module.css suffix will be treated as CSS modules
* and scoped locally.
*/
.title {
font-size: rem(34px);
font-weight: 900;
@media (max-width: $mantine-breakpoint-sm) {
font-size: rem(24px);
}
}
.description {
max-width: rem(600px);
margin: auto;
&::after {
content: '';
display: block;
background-color: #674ad6;
width: rem(45px);
height: rem(2px);
margin-top: var(--mantine-spacing-sm);
margin-left: auto;
margin-right: auto;
}
}
.card {
border: rem(1px) solid light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5));
}
.cardTitle {
&::after {
content: '';
display: block;
background-color: #674ad6;
width: rem(45px);
height: rem(2px);
margin-top: var(--mantine-spacing-sm);
}
}
.social_card {
position: relative;
cursor: pointer;
overflow: hidden;
transition:
transform 150ms ease,
box-shadow 100ms ease;
padding: var(--mantine-spacing-xl);
padding-left: calc(var(--mantine-spacing-xl) * 1);
margin-bottom: 1rem;
background-color: #000;
@mixin hover {
box-shadow: var(--mantine-shadow-md);
transform: scale(1.02);
}
&::before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: rem(6px);
}
}

45
docs/tsconfig.json Normal file
View File

@@ -0,0 +1,45 @@
{
"compilerOptions": {
"baseUrl": ".",
"target": "ESNext",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"paths": {
"@/.source": [
"./.source/index.ts"
],
"@/*": [
"./src/*"
]
},
"plugins": [
{
"name": "next"
}
]
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}

View File

File diff suppressed because it is too large Load Diff