.ts
TypeScript
(application/typescript)
// 3rd-party
import React from "react";
import type { ReactView } from "@ethicdevs/react-monolith";
// import styled from "styled-components";

// app
import type { CommonProps } from "../types";
import { AppRoute } from "../routes.defs";
import { Button } from "../components/Button.styled";
import { Card } from "../components/Card.styled";
import { Grid } from "../components/Grid";
import { Layout } from "../components/Layout";
import { PageWrapper } from "../components/PageWrapper";
import {} from "../components";
import { buildRouteLink } from "../utils/shared";

export interface HomeViewProps extends CommonProps {
  foo?: boolean;
}

// ReadMe tagline for consistency
const tagline =
  "GitFOSS is a small and simple, free and open-source Git Forge built w/ Node.JS, Fastify, Prisma and React!";

type FeatureCard = {
  title: string;
  description: string;
  icon?: string;
  image?: string;
};

const features: FeatureCard[] = [
  {
    title: "Free hosted or self-hosted",
    description:
      "Free hosting for everyone, or self-hosted deployments, both free.",
    icon: "🏠",
  },
  {
    title: "MIT Licensed Open Source",
    description: "Fully Open Source Software licensed under the MIT License.",
    icon: "βš–οΈ",
  },
  {
    title: "Modern tech stack",
    description:
      "Written with React, TypeScript, Prisma, and MVC architecture.",
    icon: "🧰",
  },
  {
    title: "Git repo browser",
    description: "Files, branches, and tags are browsable in the UI.",
    icon: "πŸ—‚οΈ",
  },
  {
    title: "PRs & merges",
    description: "Comment, review, merge, and delete source branches.",
    icon: "πŸ”€",
    image: "https://i.ibb.co/xp1bfGG/image.png",
  },
  {
    title: "Fork repositories",
    description: "Fork repository workflow support for collaboration.",
    icon: "🍴",
    image: "https://i.ibb.co/1Ghq4j73/image.png",
  },
  {
    title: "Tests & Coverage UI",
    description: "Browser-based tests and coverage details.",
    icon: "βœ…",
    image: "https://i.ibb.co/bgvkWwFh/image.png",
  },
  {
    title: "Builds & CI",
    description: "Pipelines, actions, and a GitFOSS runner for CI.",
    icon: "πŸ—οΈ",
    image: "https://i.ibb.co/bgvkWwFh/image.png",
  },
  {
    title: "Deploy",
    description: "Pipelines and continuous deployment capabilities.",
    icon: "πŸš€",
    image: "https://i.ibb.co/bgvkWwFh/image.png",
  },
  {
    title: "API Reference",
    description: "Auto-generated API reference from repos and docs.",
    icon: "πŸ“š",
    image: "https://i.ibb.co/bgvkWwFh/image.png",
  },
  {
    title: "AI assistant",
    description:
      "Small no-context model leveraging repo docs, API reference, and docs to assist.",
    icon: "πŸ€–",
    image: "https://i.ibb.co/bgvkWwFh/image.png",
  },
  {
    title: "SSH Keys management",
    description: "SSH keys management for secure access to repos.",
    icon: "πŸ”‘",
    image: "https://i.ibb.co/bgvkWwFh/image.png",
  },
  {
    title: "User profiles",
    description:
      "Profiles with starred/followed repos, followers, and user readmes/links.",
    icon: "πŸ§‘β€πŸ’»",
    image: "https://i.ibb.co/bgvkWwFh/image.png",
  },
];

// Helper to generate a richer description for "rest" features
function generateDescription(base: string, idx: number): string {
  const extras = [
    "This feature is integrated with a modern, scalable workflow.",
    "Discover how this can accelerate your team’s productivity.",
    "Includes settings and CLI for automation.",
  ];
  const extra = extras[idx % extras.length];
  return `${base} ${extra}`;
}

const HomeView: ReactView<HomeViewProps> = (props) => {
  const { commonProps } = props;
  const primary = features.slice(0, 4); // top 4 features in cards
  const rest = features.slice(4); // remaining features in sections

  return (
    <Layout {...commonProps}>
      <PageWrapper style={{ maxWidth: 960, margin: "0 auto" }}>
        {/* Hero Section */}
        <Grid.Row fluid style={{ minHeight: "72vh", alignItems: "center" }}>
          <Grid.Col
            fluid
            nowrap
            justifyContent={"center"}
            style={{
              marginTop: 80,
              paddingRight: 20,
              minWidth: 420,
            }}
          >
            <h1 style={{ fontSize: "3rem", margin: 0, lineHeight: 1.05 }}>
              GitFOSS
            </h1>
            <p style={{ fontSize: "1.15rem", color: "#555", maxWidth: 600 }}>
              {tagline}
            </p>
            <Grid.Row fluid gap={16} style={{ marginTop: 20 }}>
              <Button as="a" href={buildRouteLink(AppRoute.AUTH_REGISTER, {})}>
                Register
              </Button>
              <Button as="a" href={buildRouteLink(AppRoute.AUTH_LOGIN, {})}>
                Login
              </Button>
              <Button
                as="a"
                href={buildRouteLink(AppRoute.REPOSITORY_EXPLORE, {})}
              >
                Explore
              </Button>
            </Grid.Row>
          </Grid.Col>
          <img
            src="https://i.ibb.co/6cCs4VgG/image.png"
            alt="GitFOSS hero illustration"
            style={{
              width: "100%",
              maxWidth: 500,
              minWidth: 400,
              height: "auto",
              marginTop: 24,
              objectFit: "contain",
              borderRadius: 24,
            }}
          />
        </Grid.Row>

        {/* Primary features: 4 cards in a row, using Card component */}
        <Grid.Row fluid gap={16} style={{ padding: "10vh 0 20vh 0" }}>
          {primary.map((f, idx) => (
            <Grid.Col key={idx} fluid nowrap>
              <Card
                themeScheme={commonProps.themeScheme}
                style={{ minHeight: 238, minWidth: 210 }}
              >
                <div
                  style={{
                    display: "flex",
                    flexDirection: "column",
                    gap: 12,
                  }}
                >
                  <div
                    className="icon"
                    aria-label={f.title}
                    style={{ fontSize: 48 }}
                  >
                    {f.icon ?? "✨"}
                  </div>
                  <div
                    className="feature-title"
                    style={{ fontWeight: 700, fontSize: 20 }}
                  >
                    {f.title}
                  </div>
                  <div className="feature-desc" style={{ color: "#777777" }}>
                    {f.description}
                  </div>
                </div>
              </Card>
            </Grid.Col>
          ))}
        </Grid.Row>

        {/* Rest features: as sections with alternating image/text */}
        {rest.map((f, idx) => {
          const genDesc = generateDescription(f.description, idx);
          const isEven = idx % 2 === 0;
          const imageSrc = f.image ?? `/assets/feature-${idx + 4}.png`;

          return (
            <section
              key={idx}
              aria-label={`Feature: ${f.title}`}
              style={{ padding: "40px 0", minHeight: "80vh" }}
            >
              <div
                style={{
                  display: "flex",
                  alignItems: "center",
                  gap: 48,
                  flexWrap: "wrap",
                }}
              >
                {isEven && (
                  <>
                    <div style={{ flex: 1, minWidth: 260 }}>
                      <img
                        src={imageSrc}
                        alt={`${f.title} illustration`}
                        style={{
                          width: "100%",
                          height: "auto",
                          borderRadius: 24,
                        }}
                      />
                    </div>
                    <div style={{ flex: 1, minWidth: 260 }}>
                      <h1 style={{ margin: 0, fontSize: 48 }}>{f.title}</h1>
                      <h3
                        className="feature-desc"
                        style={{
                          color: "#777777",
                          lineHeight: 1.4,
                          fontWeight: "normal",
                        }}
                      >
                        {genDesc}
                      </h3>
                      <div
                        className="cta"
                        style={{ display: "flex", gap: 16, marginTop: 16 }}
                      >
                        <Button
                          as="a"
                          href={buildRouteLink(AppRoute.AUTH_REGISTER, {})}
                        >
                          Register
                        </Button>
                        <Button
                          as="a"
                          href={buildRouteLink(AppRoute.AUTH_LOGIN, {})}
                        >
                          Login
                        </Button>
                      </div>
                    </div>
                  </>
                )}
                {!isEven && (
                  <>
                    <div style={{ flex: 1, minWidth: 260 }}>
                      <h1 style={{ margin: 0, fontSize: 48 }}>{f.title}</h1>
                      <h3
                        className="feature-desc"
                        style={{
                          color: "#777777",
                          lineHeight: 1.4,
                          fontWeight: "normal",
                        }}
                      >
                        {genDesc}
                      </h3>
                      <div
                        className="cta"
                        style={{ display: "flex", gap: 16, marginTop: 16 }}
                      >
                        <Button
                          as="a"
                          href={buildRouteLink(AppRoute.AUTH_REGISTER, {})}
                        >
                          Register
                        </Button>
                        <Button
                          as="a"
                          href={buildRouteLink(AppRoute.AUTH_LOGIN, {})}
                        >
                          Login
                        </Button>
                      </div>
                    </div>
                    <div style={{ flex: 1, minWidth: 260 }}>
                      <img
                        src={imageSrc}
                        alt={`${f.title} illustration`}
                        style={{
                          width: "100%",
                          height: "auto",
                          borderRadius: 24,
                        }}
                      />
                    </div>
                  </>
                )}
              </div>
            </section>
          );
        })}

        {/* Contribute section outside loop */}
        <section
          aria-label="Contribute to GitFOSS"
          style={{
            padding: "48px 0",
            borderTop: "2px dashed #4e7b50",
            borderBottom: "2px dashed #4e7b50",
            width: "100%",
          }}
        >
          <Grid.Col
            fluid
            nowrap
            justifyContent={"center"}
            alignItems={"center"}
          >
            <h1
              style={{
                margin: "0 0 8px",
                maxWidth: 400,
                fontSize: 48,
                textAlign: "center",
              }}
            >
              Contribute to GitFOSS
            </h1>
            <h3
              style={{
                color: "#555",
                margin: "16px 0",
                maxWidth: 400,
                textAlign: "center",
                fontWeight: "normal",
              }}
            >
              Help grow GitFOSS by contributing code, docs, and governance. Your
              involvement keeps the project open, fast, and secure.
            </h3>
            <Button
              as="a"
              href="/ethicdevs/gitfoss"
              style={{ width: 300, height: 60, fontSize: 24, marginTop: 24 }}
            >
              Contribute Now
            </Button>
          </Grid.Col>
        </section>
      </PageWrapper>
    </Layout>
  );
};

HomeView.displayName = "HomeView";
export default HomeView;