Use Nextra for docs (#1140)
Use https://github.com/shuding/nextra and https://github.com/leoMirandaa/shadcn-landing-page instead of Docusaurus.
2
.github/workflows/docs-build.yml
vendored
@@ -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
|
||||
|
||||
5
.github/workflows/docs-publish.yml
vendored
@@ -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
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": ["next/core-web-vitals", "next/typescript"]
|
||||
}
|
||||
36
docs/.gitignore
vendored
@@ -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/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
27
docs/app/docs/[[...mdxPath]]/page.tsx
Normal 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
@@ -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
@@ -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
@@ -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 />*/}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
presets: [require.resolve("@docusaurus/core/lib/babel/preset")],
|
||||
};
|
||||
16
docs/components.json
Normal 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
@@ -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>
|
||||
);
|
||||
};
|
||||
96
docs/components/AdvancedFeatures.tsx
Normal 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
@@ -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
@@ -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>
|
||||
);
|
||||
};
|
||||
75
docs/components/Features.tsx
Normal 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
@@ -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>
|
||||
);
|
||||
};
|
||||
24
docs/components/GitHub.tsx
Normal 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
@@ -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>
|
||||
);
|
||||
};
|
||||
67
docs/components/HowItWorks.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
15
docs/components/PreviewImage.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
38
docs/components/Statistics.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
60
docs/components/ui/accordion.tsx
Normal 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 };
|
||||
36
docs/components/ui/badge.tsx
Normal 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 };
|
||||
56
docs/components/ui/button.tsx
Normal 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 };
|
||||
86
docs/components/ui/card.tsx
Normal 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,
|
||||
};
|
||||
138
docs/components/ui/sheet.tsx
Normal 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,
|
||||
};
|
||||
@@ -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.
|
||||
115
docs/content/community/contributing.mdx
Normal 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 -->*/}
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
---
|
||||
title: Cloud services
|
||||
---
|
||||
# Cloud services
|
||||
|
||||
## Vercel
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
title: Systemd
|
||||
---
|
||||
|
||||
# Systemd
|
||||
|
||||
This section describes how to deploy Bracket (frontend and backend) as a Systemd service on Linux.
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
@@ -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).
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
title: Configuration
|
||||
---
|
||||
|
||||
|
||||
# Configuration
|
||||
|
||||
## Backend
|
||||
@@ -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.
|
||||
@@ -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`:
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
title: Terminology
|
||||
---
|
||||
|
||||
# Terminology
|
||||
|
||||
Here is a list of commonly used terms and their meanings:
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"label": "Community",
|
||||
"position": 4,
|
||||
"link": {
|
||||
"type": "generated-index"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
[](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 -->
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"label": "Deployment",
|
||||
"position": 3,
|
||||
"link": {
|
||||
"type": "generated-index"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"label": "Running Bracket",
|
||||
"position": 2,
|
||||
"link": {
|
||||
"type": "generated-index"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"label": "Usage",
|
||||
"position": 4,
|
||||
"link": {
|
||||
"type": "generated-index"
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
})
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
.wrapper {
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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
@@ -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"
|
||||
]
|
||||
}
|
||||