Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules/
.env.local
25 changes: 21 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
# breadchain.xyz
# Breadchain.xyz

install deps from repo root
## Getting Started

```sh
pnpm add --filter *PACKAGE_NAME* *COMMAND* *FLAG*
This is a monorepo project using pnpm as the package manager built with Astro. The actual breadchain.xyz website code is in the `apps/website` directory.

### Prerequisites

- [pnpm](https://pnpm.io/) (version 9.1.4)

### Available Commands

- `pnpm website` - Run the website locally
- `pnpm website:ui` - Run the website UI
- `pnpm website:build` - Build the website
- `pnpm website:test` - Run Playwright tests

## Run it locally

```
cd apps/website
pnpm install
pnpm run dev
```
3 changes: 3 additions & 0 deletions apps/website/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
MAILCHIMP_API_KEY=
MAILCHIMP_AUDIENCE_ID=
MAILCHIMP_DATA_CENTER=
4 changes: 3 additions & 1 deletion apps/website/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { defineConfig } from "astro/config";
import react from "@astrojs/react";

import tailwind from "@astrojs/tailwind";
import netlify from "@astrojs/netlify";

// https://astro.build/config
export default defineConfig({
integrations: [react(), tailwind()],
output: "server",
adapter: netlify(),
});
3 changes: 2 additions & 1 deletion apps/website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
},
"dependencies": {
"@astrojs/check": "^0.7.0",
"@astrojs/netlify": "^6.3.2",
"@astrojs/react": "^3.4.0",
"@astrojs/tailwind": "^5.1.0",
"@breadchain.xyz/shared": "workspace:*",
Expand All @@ -19,7 +20,7 @@
"@fontsource/red-hat-text": "^5.0.15",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"astro": "^4.9.2",
"astro": "^5.0.9",
"clsx": "^2.1.1",
"embla-carousel-react": "^8.5.2",
"react": "^18.3.1",
Expand Down
2 changes: 1 addition & 1 deletion apps/website/src/components/ButtonLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interface IProps {

export function ButtonLink({ children, href, isExternal }: IProps) {
const classes =
"rounded inline-block bg-breadpink-500 font-redhat font-bold px-4 py-2.5 md:px-6 md:py-3 text-breadgray-100 text-xl";
"rounded inline-block bg-breadpink-500 hover:bg-breadpink-300 font-redhat font-bold px-4 py-2.5 md:px-6 md:py-3 text-breadgray-100 text-xl transition-all duration-300";

if (isExternal)
return (
Expand Down
37 changes: 18 additions & 19 deletions apps/website/src/components/CTASection.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
import { ButtonLink } from "./ButtonLink";
import { TertiaryLink } from "./TertiaryLink";
import { SectionCard } from "./SectionCard";

export function CTASection() {
return (
<section className="px-4 pt-16 pb-32 sm:px-6 md:px-8">
<div className="m-auto max-w-4xl rounded-cta dark:bg-breadgray-darkest dark:from-breadgray-cta-bg flex flex-col gap-2 bg-white px-6 py-12 dark:bg-gradient-to-r sm:px-12">
<h2 className="font-poppins text-4xl leading-relaxed tracking-wider md:text-5xl">
Bake <span className="font-bold">$BREAD</span> with us.
</h2>
<p>
If you want to see a post-capitalist future, show your support by
baking (minting) bread. If you’re a like-minded project creating a
more progressive future, join us.
</p>
<div className="flex flex-wrap items-center gap-6 pt-8">
<ButtonLink href="https://app.breadchain.xyz" isExternal>
Get Bread
</ButtonLink>
<TertiaryLink href="https://guild.xyz/breadchain" isExternal>
Join the guild
</TertiaryLink>
</div>
<SectionCard id="cta">
<h2 className="py-4 text-4xl leading-relaxed tracking-wider md:text-5xl">
Bake <span className="font-bold">$BREAD</span> with us.
</h2>
<p>
If you want to see a post-capitalist future, show your support by baking
(minting) bread. If you’re a like-minded project creating a more
progressive future, join us.
</p>
<div className="flex flex-wrap justify-center items-center gap-6 pt-8">
<ButtonLink href="https://app.breadchain.xyz" isExternal>
Get Bread
</ButtonLink>
<TertiaryLink href="https://guild.xyz/breadchain" isExternal>
Join the guild
</TertiaryLink>
</div>
</section>
</SectionCard>
);
}
13 changes: 12 additions & 1 deletion apps/website/src/components/DesktopNavigation.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
import type { ReactNode } from "react";

import { useRef } from "react";
function DesktopNavigationLink(props: {
children: ReactNode;
href: string;
rel?: string;
target?: string;
}) {
const { children, ...remainingProps } = props;
const targetRef = useRef<HTMLElement | null>(null);

const handleClick = (e: React.MouseEvent) => {
e.preventDefault();
const targetId = remainingProps.href.slice(1);
targetRef.current = document.getElementById(targetId);
if (targetRef.current) {
targetRef.current.scrollIntoView({ behavior: "smooth" });
}
};
return (
<a
className="font-redhat dark:text-breadgray-light-grey dark:hover:text-breadgray-white text-breadgray-toast hover:text-breadgray-burnt active:text-breadgray-violet flex items-center px-2 text-xl font-bold leading-none tracking-wider min-[810px]:px-4"
{...remainingProps}
onClick={handleClick}
>
{children}
</a>
Expand Down
10 changes: 10 additions & 0 deletions apps/website/src/components/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type { ReactNode } from "react";
import { WRAPPER_CLASSES } from "../utils";
import { ButtonLink } from "./ButtonLink";
import { TertiaryLink } from "./TertiaryLink";
import { Link } from "./Link";
import { Email } from "./Icons/Email";

export function Hero() {
return (
Expand Down Expand Up @@ -39,6 +41,14 @@ export function Hero() {
Learn more
</TertiaryLink>
</HeroCTA>
<div className="pt-4">
<Link href="#newsletter" isInternal={true}>
<div className="flex flex-row items-center gap-3">
<Email />
<span>Join our newsletter</span>
</div>
</Link>
</div>
</div>
</div>
</section>
Expand Down
20 changes: 20 additions & 0 deletions apps/website/src/components/Icons/Email.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export function Email() {
return (
<div className="rounded-full w-5 h-5">
<svg
width="22"
height="22"
viewBox="0 0 22 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M20 0H0V16H20V0ZM2 14V2H18V14H2ZM6 4H4V6H6V8H8V10H12V8H14V6H16V4H14V6H12V8H8V6H6V4Z"
fill="currentColor"
/>
</svg>
</div>
);
}
47 changes: 47 additions & 0 deletions apps/website/src/components/Link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { ReactNode } from "react";
import { useRef } from "react";

export function Link({
children,
href,
isExternal,
isInternal,
}: {
children: ReactNode;
href: string;
isExternal?: boolean;
isInternal?: boolean;
}) {
const targetRef = useRef<HTMLElement | null>(null);

const classes =
"text-breadpink-300 hover:text-breadpink-200 hover:text-breadpink-200 tracking-wider font-bold text-xl";

const handleClick = (e: React.MouseEvent) => {
if (isInternal && href.startsWith("#")) {
e.preventDefault();
const targetId = href.slice(1);
targetRef.current = document.getElementById(targetId);
if (targetRef.current) {
targetRef.current.scrollIntoView({ behavior: "smooth" });
}
}
};

if (isExternal)
return (
<a
href={href}
className={classes}
target="_blank"
rel="noopener noreferrer"
>
{children}
</a>
);
return (
<a href={href} className={classes} onClick={handleClick}>
{children}
</a>
);
}
91 changes: 91 additions & 0 deletions apps/website/src/components/NewsletterSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { useState } from "react";
import type { FormEvent } from "react";
import { SectionCard } from "./SectionCard";

export function NewsletterSection() {
const [responseMessage, setResponseMessage] = useState("");
const [responseStatus, setResponseStatus] = useState(0);
const [email, setEmail] = useState("");

async function submit(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
const response = await fetch("/api/subscribe", {
method: "POST",
body: formData,
});
const data = await response.json();
if (data.message) {
setResponseMessage(data.message);
setResponseStatus(response.status);
setEmail("");
}
}

const getInputClass = () => {
let className =
"rounded-lg border h-[45px] px-4 py-2 focus:outline-none dark:border-breadgray-toast dark:bg-breadgray-darkest ";
if (responseStatus === 200) {
className += "focus:border-status-success";
} else if (responseStatus === 400) {
className += "focus:border-status-error";
} else {
className += "focus:border-white";
}
return className;
};

const getButtonClass = () => {
let className = "h-[45px] font-bold px-4 py-2 rounded-lg tracking-wider ";
if (responseStatus === 200) {
className += "bg-status-success text-breadgray-charcoal";
} else if (responseStatus === 400) {
className += "bg-status-error text-white";
} else {
className += "bg-breadpink-500 text-white";
}
return className;
};

return (
<SectionCard id="newsletter">
<div className="flex flex-col md:flex-row md:gap-12 md:items-center">
<div className="md:w-1/2">
<h2 className="font-bold text-[28px] md:text-[38px]">
Join our newsletter, stay up to date.
</h2>
<p className="text-breadgray-grey pt-4 md:pt-0">
Receive updates on the latest features, partnerships and new crypto
leftist initiatives.
</p>
</div>
<div className="md:w-1/2">
<div className="flex flex-wrap md:justify-end pt-8 md:pt-0">
<form
onSubmit={submit}
className="w-full md:w-[320px] flex flex-col gap-2"
>
<input
id="email"
type="email"
name="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
className={getInputClass()}
/>
<button type="submit" className={getButtonClass()}>
{responseStatus === 0 && "Subscribe"}
{responseStatus === 200 && "Subscribed!"}
{responseStatus === 400 && "Error, try again"}
</button>
{responseMessage && (
<p className="text-breadgray-grey">{responseMessage}</p>
)}
</form>
</div>
</div>
</div>
</SectionCard>
);
}
17 changes: 17 additions & 0 deletions apps/website/src/components/SectionCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { ReactNode } from "react";

export function SectionCard({
children,
id,
}: {
children: ReactNode;
id?: string;
}) {
return (
<section id={id} className="px-4 pt-16 pb-16 sm:px-6 md:px-8">
<div className="m-auto max-w-[1040px] border dark:border-breadgray-toast rounded-2xl dark:bg-breadgray-darkest dark:from-breadgray-cta-bg flex flex-col gap-2 bg-white px-6 py-4 dark:bg-gradient-to-r sm:px-12">
{children}
</div>
</section>
);
}
2 changes: 1 addition & 1 deletion apps/website/src/components/TertiaryLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interface IProps {

export function TertiaryLink({ children, href, isExternal }: IProps) {
const classes =
"inline-block font-redhat font-bold text-xl px-4 py-2.5 md:px-6 md:py-3 text-breadpink-500 hover:text-breadpink-600 active:text-breadpink-600 ";
"inline-block bg-breadpink-shaded/10 font-redhat font-bold text-xl px-4 py-2.5 md:px-6 md:py-3 text-breadpink-shaded hover:border-2 border-2 border-breadpink-shaded/0 hover:border-breadpink-shaded active:text-breadpink-600 transition-all duration-300";

if (isExternal) {
return (
Expand Down
6 changes: 6 additions & 0 deletions apps/website/src/env.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
/// <reference types="astro/client" />

interface ImportMetaEnv {
readonly MAILCHIMP_API_KEY: string;
readonly MAILCHIMP_AUDIENCE_ID: string;
readonly MAILCHIMP_DATA_CENTER: string;
}
Loading
Loading