diff --git a/.github/workflows/old-website.yml b/.github/workflows/old-website.yml new file mode 100644 index 0000000..7a1054c --- /dev/null +++ b/.github/workflows/old-website.yml @@ -0,0 +1,126 @@ +name: Website CI OLD +on: + workflow_dispatch: + pull_request: + branches: + - main + - development + paths: + - "apps/site/**" + - "packages/theme/**" + - "packages/site-ui/**" + push: + branches: + - main + - development + paths: + - "apps/site/**" + - "packages/theme/**" + - "packages/site-ui/**" + +jobs: + linting: + name: ESLint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup pnpm + uses: pnpm/action-setup@v2 + with: + version: 8.8.0 + + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 20 + cache: "pnpm" + + - name: Install Dependencies + run: pnpm site install + + - name: Lint + run: pnpm site run lint + + build: + name: Next Build + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Setup pnpm + uses: pnpm/action-setup@v2 + with: + version: 8.8.0 + + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 20 + cache: "pnpm" + # run: pnpm site install + + # - name: Setup pnpm + # uses: pnpm/action-setup@v2 + # with: + # version: 8.8.0 + + - name: Install Dependencies + run: pnpm site install + + - name: Run build + run: pnpm site run build + + test: + name: Playwright Tests + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Setup pnpm + uses: pnpm/action-setup@v2 + with: + version: 8.8.0 + + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 20 + cache: "pnpm" + # run: pnpm site install + + - name: Install Dependencies + run: pnpm site install + + - name: Run build + run: pnpm site run build + + # - name: Get pnpm store directory + # id: pnpm-cache + # shell: bash + # run: | + # echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT + + # - uses: actions/cache@v3 + # name: Setup pnpm cache + # with: + # path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} + # key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + # restore-keys: | + # ${{ runner.os }}-pnpm-store- + + - name: Install Playwright Browsers + run: pnpm site playwright install --with-deps + + - name: Run Playwright tests + run: pnpm site playwright test + + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: apps/site/playwright-report/ + retention-days: 30 diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 39e8b8a..4d53f90 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -6,17 +6,17 @@ on: - main - development paths: - - "apps/site/**" + - "apps/website/**" - "packages/theme/**" - - "packages/site-ui/**" + - "packages/shared/**" push: branches: - main - development paths: - - "apps/site/**" + - "apps/website/**" - "packages/theme/**" - - "packages/site-ui/**" + - "packages/shared/**" jobs: linting: @@ -38,13 +38,13 @@ jobs: cache: "pnpm" - name: Install Dependencies - run: pnpm site install + run: pnpm website install - name: Lint - run: pnpm site run lint + run: pnpm website run lint build: - name: Next Build + name: Astro Build runs-on: ubuntu-latest steps: - name: Checkout repo @@ -60,67 +60,46 @@ jobs: with: node-version: 20 cache: "pnpm" - # run: pnpm site install - - # - name: Setup pnpm - # uses: pnpm/action-setup@v2 - # with: - # version: 8.8.0 - - - name: Install Dependencies - run: pnpm site install - - - name: Run build - run: pnpm site run build - - test: - name: Playwright Tests - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v3 - - - name: Setup pnpm - uses: pnpm/action-setup@v2 - with: - version: 8.8.0 - - - name: Setup node - uses: actions/setup-node@v3 - with: - node-version: 20 - cache: "pnpm" - # run: pnpm site install - name: Install Dependencies - run: pnpm site install + run: pnpm website install - name: Run build - run: pnpm site run build - - # - name: Get pnpm store directory - # id: pnpm-cache - # shell: bash - # run: | - # echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT - - # - uses: actions/cache@v3 - # name: Setup pnpm cache - # with: - # path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} - # key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - # restore-keys: | - # ${{ runner.os }}-pnpm-store- - - - name: Install Playwright Browsers - run: pnpm site playwright install --with-deps - - - name: Run Playwright tests - run: pnpm site playwright test - - - uses: actions/upload-artifact@v3 - if: always() - with: - name: playwright-report - path: apps/site/playwright-report/ - retention-days: 30 + run: pnpm website run build + + # test: + # name: Playwright Tests + # runs-on: ubuntu-latest + # steps: + # - name: Checkout repo + # uses: actions/checkout@v3 + + # - name: Setup pnpm + # uses: pnpm/action-setup@v2 + # with: + # version: 8.8.0 + + # - name: Setup node + # uses: actions/setup-node@v3 + # with: + # node-version: 20 + # cache: "pnpm" + + # - name: Install Dependencies + # run: pnpm website install + + # - name: Run build + # run: pnpm website run build + + # - name: Install Playwright Browsers + # run: pnpm site playwright install --with-deps + + # - name: Run Playwright tests + # run: pnpm site playwright test + + # - uses: actions/upload-artifact@v3 + # if: always() + # with: + # name: playwright-report + # path: apps/site/playwright-report/ + # retention-days: 30 diff --git a/apps/app/.gitignore b/apps/app/.gitignore index fd3dbb5..76ba8a7 100644 --- a/apps/app/.gitignore +++ b/apps/app/.gitignore @@ -34,3 +34,6 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# wagmi cli +generated.ts \ No newline at end of file diff --git a/apps/app/package.json b/apps/app/package.json index bc9c922..ebdf7ce 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -4,6 +4,7 @@ "version": "0.1.0", "private": true, "scripts": { + "predev": "wagmi generate", "dev": "next dev", "build": "next build", "start": "next start", @@ -11,18 +12,23 @@ }, "dependencies": { "@breadchain.xyz/theme": "workspace:*", + "@rainbow-me/rainbowkit": "^2.1.0", + "@tanstack/react-query": "^5.37.1", + "next": "14.2.3", "react": "^18", "react-dom": "^18", - "next": "14.2.3" + "viem": "2.x", + "wagmi": "^2.9.2" }, "devDependencies": { - "typescript": "^5", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "@wagmi/cli": "^2.1.7", + "eslint": "^8", + "eslint-config-next": "14.2.3", "postcss": "^8", "tailwindcss": "^3.4.1", - "eslint": "^8", - "eslint-config-next": "14.2.3" + "typescript": "^5" } } diff --git a/apps/app/src/abi/breadToken.json b/apps/app/src/abi/breadToken.json new file mode 100644 index 0000000..0cae79c --- /dev/null +++ b/apps/app/src/abi/breadToken.json @@ -0,0 +1,105 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "implementationAddress", + "type": "address" + }, + { "internalType": "address", "name": "ownerAddress", "type": "address" }, + { "internalType": "bytes", "name": "data", "type": "bytes" } + ], + "stateMutability": "payable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "ProxyAdminTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousImplementation", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "ProxyImplementationUpdated", + "type": "event" + }, + { "stateMutability": "payable", "type": "fallback" }, + { + "inputs": [], + "name": "proxyAdmin", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "bytes4", "name": "id", "type": "bytes4" }], + "name": "supportsInterface", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "newAdmin", "type": "address" } + ], + "name": "transferProxyAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { "internalType": "bytes", "name": "data", "type": "bytes" } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { "stateMutability": "payable", "type": "receive" } +] diff --git a/apps/app/src/app/bakery/BakeryPage.tsx b/apps/app/src/app/bakery/BakeryPage.tsx new file mode 100644 index 0000000..b668140 --- /dev/null +++ b/apps/app/src/app/bakery/BakeryPage.tsx @@ -0,0 +1,12 @@ +"use client"; + +import { ConnectButton } from "@rainbow-me/rainbowkit"; +import { useAccount } from "wagmi"; + +export function BakeryPage() { + const { address } = useAccount(); + + return ( +
{!address ? :

Connected as {address}

}
+ ); +} diff --git a/apps/app/src/app/core/context/Providers.tsx b/apps/app/src/app/core/context/Providers.tsx new file mode 100644 index 0000000..5b2e488 --- /dev/null +++ b/apps/app/src/app/core/context/Providers.tsx @@ -0,0 +1,19 @@ +"use client"; +import { ReactNode } from "react"; +import { WagmiProvider } from "wagmi"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; + +import { wagmiConfig } from "@/wagmi/config"; +import { RainbowKitProvider } from "@rainbow-me/rainbowkit"; + +const queryClient = new QueryClient(); + +export function Providers({ children }: { children: ReactNode }) { + return ( + + + {children} + + + ); +} diff --git a/apps/app/src/app/layout.tsx b/apps/app/src/app/layout.tsx index 3314e47..b960398 100644 --- a/apps/app/src/app/layout.tsx +++ b/apps/app/src/app/layout.tsx @@ -1,14 +1,11 @@ -import type { Metadata } from "next"; import { Inter } from "next/font/google"; + +import { Providers } from "./core/context/Providers"; + import "./globals.css"; const inter = Inter({ subsets: ["latin"] }); -export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", -}; - export default function RootLayout({ children, }: Readonly<{ @@ -16,7 +13,9 @@ export default function RootLayout({ }>) { return ( - {children} + + {children} + ); } diff --git a/apps/app/src/app/page.tsx b/apps/app/src/app/page.tsx index 2acfd44..2d58b16 100644 --- a/apps/app/src/app/page.tsx +++ b/apps/app/src/app/page.tsx @@ -1,113 +1,12 @@ -import Image from "next/image"; +import type { Metadata } from "next"; -export default function Home() { - return ( -
-
-

- Get started by editing  - src/app/page.tsx -

-
- - By{" "} - Vercel Logo - -
-
- -
- Next.js Logo -
- -
- -

- Docs{" "} - - -> - -

-

- Find in-depth information about Next.js features and API. -

-
+import { BakeryPage } from "./bakery/BakeryPage"; - -

- Learn{" "} - - -> - -

-

- Learn about Next.js in an interactive course with quizzes! -

-
+export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; - -

- Templates{" "} - - -> - -

-

- Explore starter templates for Next.js. -

-
- - -

- Deploy{" "} - - -> - -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-
-
-
- ); +export default function Home() { + return ; } diff --git a/apps/app/src/wagmi/config.ts b/apps/app/src/wagmi/config.ts new file mode 100644 index 0000000..06c819b --- /dev/null +++ b/apps/app/src/wagmi/config.ts @@ -0,0 +1,12 @@ +import { getDefaultConfig } from "@rainbow-me/rainbowkit"; +import { gnosis } from "wagmi/chains"; + +const projectId = process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID; +if (!projectId) + throw new Error("Missing env var: NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID"); + +export const wagmiConfig = getDefaultConfig({ + appName: "Breadchain Crowdstaking", + projectId, + chains: [gnosis], +}); diff --git a/apps/app/wagmi.config.ts b/apps/app/wagmi.config.ts new file mode 100644 index 0000000..a08fe21 --- /dev/null +++ b/apps/app/wagmi.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from "@wagmi/cli"; +import { react } from "@wagmi/cli/plugins"; +import { Abi } from "viem"; + +import breadToken from "./src/abi/breadToken.json"; + +export default defineConfig({ + out: "src/wagmi/generated.ts", + contracts: [ + { + name: "Bread", + address: "0xa555d5344f6FB6c65da19e403Cb4c1eC4a1a5Ee3", + abi: breadToken as Abi, + }, + ], + plugins: [react()], +}); diff --git a/apps/website/.gitignore b/apps/website/.gitignore new file mode 100644 index 0000000..016b59e --- /dev/null +++ b/apps/website/.gitignore @@ -0,0 +1,24 @@ +# build output +dist/ + +# generated types +.astro/ + +# dependencies +node_modules/ + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# environment variables +.env +.env.production + +# macOS-specific files +.DS_Store + +# jetbrains setting folder +.idea/ diff --git a/apps/website/.vscode/extensions.json b/apps/website/.vscode/extensions.json new file mode 100644 index 0000000..22a1505 --- /dev/null +++ b/apps/website/.vscode/extensions.json @@ -0,0 +1,4 @@ +{ + "recommendations": ["astro-build.astro-vscode"], + "unwantedRecommendations": [] +} diff --git a/apps/website/.vscode/launch.json b/apps/website/.vscode/launch.json new file mode 100644 index 0000000..d642209 --- /dev/null +++ b/apps/website/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "command": "./node_modules/.bin/astro dev", + "name": "Development server", + "request": "launch", + "type": "node-terminal" + } + ] +} diff --git a/apps/website/README.md b/apps/website/README.md new file mode 100644 index 0000000..1db3fb3 --- /dev/null +++ b/apps/website/README.md @@ -0,0 +1,54 @@ +# Astro Starter Kit: Basics + +```sh +npm create astro@latest -- --template basics +``` + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics) +[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics) +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json) + +> πŸ§‘β€πŸš€ **Seasoned astronaut?** Delete this file. Have fun! + +![just-the-basics](https://github.com/withastro/astro/assets/2244813/a0a5533c-a856-4198-8470-2d67b1d7c554) + +## πŸš€ Project Structure + +Inside of your Astro project, you'll see the following folders and files: + +```text +/ +β”œβ”€β”€ public/ +β”‚ └── favicon.svg +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ components/ +β”‚ β”‚ └── Card.astro +β”‚ β”œβ”€β”€ layouts/ +β”‚ β”‚ └── Layout.astro +β”‚ └── pages/ +β”‚ └── index.astro +└── package.json +``` + +Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. + +There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. + +Any static assets, like images, can be placed in the `public/` directory. + +## 🧞 Commands + +All commands are run from the root of the project, from a terminal: + +| Command | Action | +| :------------------------ | :----------------------------------------------- | +| `npm install` | Installs dependencies | +| `npm run dev` | Starts local dev server at `localhost:4321` | +| `npm run build` | Build your production site to `./dist/` | +| `npm run preview` | Preview your build locally, before deploying | +| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | +| `npm run astro -- --help` | Get help using the Astro CLI | + +## πŸ‘€ Want to learn more? + +Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). diff --git a/apps/website/astro.config.mjs b/apps/website/astro.config.mjs new file mode 100644 index 0000000..7ece7e9 --- /dev/null +++ b/apps/website/astro.config.mjs @@ -0,0 +1,9 @@ +import { defineConfig } from 'astro/config'; +import react from "@astrojs/react"; + +import tailwind from "@astrojs/tailwind"; + +// https://astro.build/config +export default defineConfig({ + integrations: [react(), tailwind()] +}); \ No newline at end of file diff --git a/apps/website/netlify.toml b/apps/website/netlify.toml new file mode 100644 index 0000000..5ce184a --- /dev/null +++ b/apps/website/netlify.toml @@ -0,0 +1,2 @@ +[build] +ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF apps/website packages/theme packages/shared" diff --git a/apps/website/package.json b/apps/website/package.json new file mode 100644 index 0000000..90197d8 --- /dev/null +++ b/apps/website/package.json @@ -0,0 +1,29 @@ +{ + "name": "@breadchain.xyz/website", + "type": "module", + "version": "0.0.1", + "scripts": { + "dev": "astro dev", + "start": "astro dev", + "build": "astro check && astro build", + "preview": "astro preview", + "astro": "astro" + }, + "dependencies": { + "@astrojs/check": "^0.7.0", + "@astrojs/react": "^3.4.0", + "@astrojs/tailwind": "^5.1.0", + "@breadchain.xyz/shared": "workspace:*", + "@breadchain.xyz/theme": "workspace:*", + "@fontsource/poppins": "^5.0.8", + "@fontsource/red-hat-text": "^5.0.15", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "astro": "^4.9.2", + "clsx": "^2.1.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "tailwindcss": "^3.4.3", + "typescript": "^5.4.5" + } +} diff --git a/apps/website/public/cca_banner.png b/apps/website/public/cca_banner.png new file mode 100644 index 0000000..56a2ce5 Binary files /dev/null and b/apps/website/public/cca_banner.png differ diff --git a/apps/website/public/favicon.svg b/apps/website/public/favicon.svg new file mode 100644 index 0000000..f157bd1 --- /dev/null +++ b/apps/website/public/favicon.svg @@ -0,0 +1,9 @@ + + + + diff --git a/apps/website/public/hero_bread_dark.png b/apps/website/public/hero_bread_dark.png new file mode 100644 index 0000000..a678e08 Binary files /dev/null and b/apps/website/public/hero_bread_dark.png differ diff --git a/apps/website/public/hero_bread_light.png b/apps/website/public/hero_bread_light.png new file mode 100644 index 0000000..6a08966 Binary files /dev/null and b/apps/website/public/hero_bread_light.png differ diff --git a/apps/website/public/labordao_banner.png b/apps/website/public/labordao_banner.png new file mode 100644 index 0000000..469eeb7 Binary files /dev/null and b/apps/website/public/labordao_banner.png differ diff --git a/apps/website/public/symbiota_banner.png b/apps/website/public/symbiota_banner.png new file mode 100644 index 0000000..db49258 Binary files /dev/null and b/apps/website/public/symbiota_banner.png differ diff --git a/apps/website/src/components/ButtonLink.tsx b/apps/website/src/components/ButtonLink.tsx new file mode 100644 index 0000000..b12e234 --- /dev/null +++ b/apps/website/src/components/ButtonLink.tsx @@ -0,0 +1,30 @@ +import type { ReactNode } from "react"; + +interface IProps { + children: ReactNode; + href: string; + isExternal?: boolean; +} + +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"; + + if (isExternal) + return ( + + {children} + + ); + + return ( + + {children} + + ); +} diff --git a/apps/website/src/components/CTASection.tsx b/apps/website/src/components/CTASection.tsx new file mode 100644 index 0000000..0bd4492 --- /dev/null +++ b/apps/website/src/components/CTASection.tsx @@ -0,0 +1,27 @@ +import { ButtonLink } from "./ButtonLink"; +import { TertiaryLink } from "./TertiaryLink"; + +export function CTASection() { + return ( +
+
+

+ Bake $BREAD with us. +

+

+ 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. +

+
+ + Get Bread + + + Join the guild + +
+
+
+ ); +} diff --git a/apps/website/src/components/ColorToggle.tsx b/apps/website/src/components/ColorToggle.tsx new file mode 100644 index 0000000..d428a8d --- /dev/null +++ b/apps/website/src/components/ColorToggle.tsx @@ -0,0 +1,99 @@ +import { useEffect } from "react"; + +type TColorMode = "LIGHT" | "DARK"; + +export function ColorToggle() { + useEffect(() => { + const colorMode = localStorage.getItem("colorMode") as TColorMode; + if (colorMode === "DARK") { + document.documentElement.classList.add("dark"); + return; + } + if (colorMode === "LIGHT") return; + if ( + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches + ) { + document.documentElement.classList.add("dark"); + } + }, []); + + function handleColorToggle() { + if (document.documentElement.classList.contains("dark")) { + document.documentElement.classList.remove("dark"); + localStorage.setItem("colorMode", "LIGHT"); + } else { + localStorage.setItem("colorMode", "DARK"); + document.documentElement.classList.add("dark"); + } + } + + return ( + + ); +} + +function MoonIcon() { + return ( + + + + ); +} + +function SunIcon() { + return ( + + + + + + + + + + + ); +} + +function Divider() { + return ( + + + + ); +} diff --git a/apps/website/src/components/DesktopNavigation.tsx b/apps/website/src/components/DesktopNavigation.tsx new file mode 100644 index 0000000..eb88b2a --- /dev/null +++ b/apps/website/src/components/DesktopNavigation.tsx @@ -0,0 +1,34 @@ +import type { ReactNode } from "react"; + +function DesktopNavigationLink(props: { + children: ReactNode; + href: string; + rel?: string; + target?: string; +}) { + const { children, ...remainingProps } = props; + return ( + + {children} + + ); +} + +export function DesktopNavigation() { + return ( + + ); +} diff --git a/apps/website/src/components/Footer.tsx b/apps/website/src/components/Footer.tsx new file mode 100644 index 0000000..51f7386 --- /dev/null +++ b/apps/website/src/components/Footer.tsx @@ -0,0 +1,172 @@ +import clsx from "clsx"; +import type { ReactNode } from "react"; +import { WRAPPER_CLASSES } from "../utils"; +import { LogoSVG } from "@breadchain.xyz/shared/icons"; + +function FooterSection({ children }: { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} + +function SocialsSection({ children }: { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} + +function FooterLogoSection({ children }: { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} + +function FooterSectionTitle({ children }: { children: ReactNode }) { + return ( +

+ {children} +

+ ); +} + +function FooterSectionList({ children }: { children: ReactNode }) { + return ( + + ); +} + +function FooterGrid({ children }: { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} + +export function FooterLink({ + children, + href, + isExternal, +}: { + children: ReactNode; + href: string; + isExternal?: boolean; +}) { + const classes = + "dark:text-breadgray-white dark:hover:text-breadgray-ultra-white"; + + if (isExternal) + return ( + + {children} + + ); + return ( + + {children} + + ); +} + +export function Footer() { + return ( + + ); +} diff --git a/apps/website/src/components/Header.tsx b/apps/website/src/components/Header.tsx new file mode 100644 index 0000000..c602c46 --- /dev/null +++ b/apps/website/src/components/Header.tsx @@ -0,0 +1,57 @@ +import { useState } from "react"; +import clsx from "clsx"; + +import { LogoSVG } from "@breadchain.xyz/shared/icons"; + +import { ButtonLink } from "./ButtonLink"; +import { DesktopNavigation } from "./DesktopNavigation"; +import { ColorToggle } from "./ColorToggle"; +import { MobileNavigation } from "./MobileNavigation"; +import type { ToggleType } from "./MobileNavigationToggle"; +import { WRAPPER_CLASSES } from "../utils"; + +export function Header() { + const [isMobNavOpen, setIsMobNavOpen] = useState(false); + const [toggleType, setToggleType] = useState(null); + + const handleNavToggle = (type: ToggleType | undefined) => { + if (type) setToggleType(type); + setIsMobNavOpen(!isMobNavOpen); + }; + + function clearToggleType() { + setToggleType(null); + } + return ( +
+
+
+ +
+ +
+ +
+ +
+
+
+ + Get Bread + +
+
+ +
+ ); +} diff --git a/apps/website/src/components/Hero.tsx b/apps/website/src/components/Hero.tsx new file mode 100644 index 0000000..817ea71 --- /dev/null +++ b/apps/website/src/components/Hero.tsx @@ -0,0 +1,93 @@ +import clsx from "clsx"; +import type { ReactNode } from "react"; +import { WRAPPER_CLASSES } from "../utils"; +import { ButtonLink } from "./ButtonLink"; +import { TertiaryLink } from "./TertiaryLink"; + +export function Hero() { + return ( +
+
+
+ +
+ +
+ + $breadchain + + The currency for solidarity + + Get $BREAD and use it anywhere - all while supporting a + post-capitalist future. + +
+
+ + + Get Bread + + + Learn more + + +
+
+
+ ); +} + +export function HeroHeading({ children }: { children: ReactNode }) { + return ( +

+ {children} +

+ ); +} + +export function HeroSubHeading({ children }: { children: ReactNode }) { + return ( +

+ {children} +

+ ); +} + +export function HeroCTA({ children }: { children: ReactNode }) { + return
{children}
; +} + +export function HeroTagline({ children }: { children: ReactNode }) { + return ( +
+ + {children} + +
+ ); +} + +function MidjourneyImage() { + return ( + <> + + + + ); +} diff --git a/apps/website/src/components/Icons/Checkmark.tsx b/apps/website/src/components/Icons/Checkmark.tsx new file mode 100644 index 0000000..572bc97 --- /dev/null +++ b/apps/website/src/components/Icons/Checkmark.tsx @@ -0,0 +1,15 @@ +export function CheckmarkIcon() { + return ( + + + + ); +} diff --git a/apps/website/src/components/Icons/Coin.tsx b/apps/website/src/components/Icons/Coin.tsx new file mode 100644 index 0000000..914b797 --- /dev/null +++ b/apps/website/src/components/Icons/Coin.tsx @@ -0,0 +1,15 @@ +export function CoinIcon() { + return ( + + + + ); +} diff --git a/apps/website/src/components/Icons/Eye.tsx b/apps/website/src/components/Icons/Eye.tsx new file mode 100644 index 0000000..88a6b19 --- /dev/null +++ b/apps/website/src/components/Icons/Eye.tsx @@ -0,0 +1,22 @@ +export function EyeIcon() { + return ( + + + + + + + + + + + ); +} diff --git a/apps/website/src/components/Icons/Message.tsx b/apps/website/src/components/Icons/Message.tsx new file mode 100644 index 0000000..05ecfdb --- /dev/null +++ b/apps/website/src/components/Icons/Message.tsx @@ -0,0 +1,15 @@ +export function MessageIcon() { + return ( + + + + ); +} diff --git a/apps/website/src/components/Icons/Reload.tsx b/apps/website/src/components/Icons/Reload.tsx new file mode 100644 index 0000000..763fd70 --- /dev/null +++ b/apps/website/src/components/Icons/Reload.tsx @@ -0,0 +1,15 @@ +export function ReloadIcon() { + return ( + + + + ); +} diff --git a/apps/website/src/components/Icons/Users.tsx b/apps/website/src/components/Icons/Users.tsx new file mode 100644 index 0000000..68df567 --- /dev/null +++ b/apps/website/src/components/Icons/Users.tsx @@ -0,0 +1,22 @@ +export function UsersIcon() { + return ( + + + + + + + + + + + ); +} diff --git a/apps/website/src/components/Icons/VerticalArrows.tsx b/apps/website/src/components/Icons/VerticalArrows.tsx new file mode 100644 index 0000000..f4dc127 --- /dev/null +++ b/apps/website/src/components/Icons/VerticalArrows.tsx @@ -0,0 +1,15 @@ +export function VerticalArrowsIcon() { + return ( + + + + ); +} diff --git a/apps/website/src/components/Icons/index.ts b/apps/website/src/components/Icons/index.ts new file mode 100644 index 0000000..82c5bca --- /dev/null +++ b/apps/website/src/components/Icons/index.ts @@ -0,0 +1,7 @@ +export * from "./Checkmark"; +export * from "./Coin"; +export * from "./Users"; +export * from "./Message"; +export * from "./Eye"; +export * from "./Reload"; +export * from "./VerticalArrows"; diff --git a/apps/website/src/components/InfoCards/InfoCards.tsx b/apps/website/src/components/InfoCards/InfoCards.tsx new file mode 100644 index 0000000..32e4eba --- /dev/null +++ b/apps/website/src/components/InfoCards/InfoCards.tsx @@ -0,0 +1,320 @@ +import { WRAPPER_PADDING } from "../../utils"; +import { CheckmarkIcon, CoinIcon, UsersIcon } from "../Icons"; +import { + InfoCardGrid, + InfoCard, + InfoCardHeading, + InfoCardIcon, + InfoCardText, + InfoCardTextContent, +} from "./ui"; +import { CardSVG1, CardSVG2, CardSVG3 } from "./ui"; +import clsx from "clsx"; + +const infoCards = [ + { + title: "Get consistent value", + infotext: + "$BREAD is linked to $DAI which is a stablecoin with equal value to USD. So $1 = 1 $BREAD.", + icon: , + BackgroundSVG: CardSVG1, + }, + { + title: "Built on solidarity", + infotext: + "A solidarity primitive is a building block for solidarity through code. Build with $BREAD to have a tech stack with values.", + icon: , + BackgroundSVG: CardSVG2, + }, + { + title: "Fund the future", + infotext: + "Earnings from the minting of $BREAD go to supporting a co-operative of post-capitalist web3 projects. ", + icon: , + BackgroundSVG: CardSVG3, + }, +]; + +export function InfoCards() { + return ( +
+
+

+ Get that $BREAD +

+
+ See why $BREAD is the best thing since sliced, well, you know. +
+
+ + {/* left col */} +
+ + + + + + + + + + + + +
+ {/* top piece */} +
+ + + + + + +
+ {/* center left col */} +
+ + + + + + + + + + + + +
+ {/* center right col */} +
+ + + + + + + + + + + + +
+ {/* bottom piece */} +
+ + + + + + +
+ {/* right col */} +
+ + + + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + + + +
+ {/* */} + {infoCards[0].icon} + + {infoCards[0].title} + {infoCards[0].infotext} + +
+
+ +
+ + {/* */} + {infoCards[1].icon} + + {infoCards[1].title} + {infoCards[1].infotext} + + +
+ +
+ +
+ + + + + + + + + + + + +
+ {/* */} + {infoCards[2].icon} + + {infoCards[2].title} + {infoCards[2].infotext} + +
+
+
+
+ ); +} diff --git a/apps/website/src/components/InfoCards/ui.tsx b/apps/website/src/components/InfoCards/ui.tsx new file mode 100644 index 0000000..9eb053c --- /dev/null +++ b/apps/website/src/components/InfoCards/ui.tsx @@ -0,0 +1,163 @@ +import type { ReactNode } from "react"; + +export function InfoCardGrid({ children }: { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} + +export function InfoCard({ children }: { children: ReactNode }) { + return ( +
+
+ {children} +
+
+ ); +} + +export function InfoCardHeading({ children }: { children: ReactNode }) { + return ( +

+ {children} +

+ ); +} + +export function InfoCardTextContent({ children }: { children: ReactNode }) { + return ( +
{children}
+ ); +} + +export function InfoCardText({ children }: { children: ReactNode }) { + return

{children}

; +} + +export function InfoCardIcon({ children }: { children: ReactNode }) { + return
{children}
; +} + +export function CardSVG1() { + return ( + + + + + + + + + + + + + + + + + + ); +} + +export function CardSVG2() { + return ( + + + + + + + + + + + + + + + + + + ); +} + +export function CardSVG3() { + return ( + + + + + + + + + + + + + + + + + + ); +} diff --git a/apps/website/src/components/MembersSection.tsx b/apps/website/src/components/MembersSection.tsx new file mode 100644 index 0000000..7547c8d --- /dev/null +++ b/apps/website/src/components/MembersSection.tsx @@ -0,0 +1,111 @@ +import clsx from "clsx"; +import { WRAPPER_CLASSES } from "../utils"; +import { TertiaryButtonLink } from "./TertiaryButtonLink"; + +import { memberProjects } from "@breadchain.xyz/shared"; + +const { cca, laborDao, symbiota } = memberProjects; + +const homepageProjects = [ + { ...cca, bannerSrc: "cca_banner.png" }, + { ...laborDao, bannerSrc: "labordao_banner.png" }, + { ...symbiota, bannerSrc: "symbiota_banner.png" }, +]; + +export function MembersSection() { + return ( +
+
+

+ Co-operative + by design +

+
+ Learn more about the project you would support. +
+
+
+ {homepageProjects.map( + ({ name, description, homepage, bannerSrc }, i) => { + return ( + + ); + } + )} +
+ +
+ ); +} + +export function MemberCard({ + name, + info, + link, + bannerSrc, +}: { + name: string; + info: string; + link: string; + bannerSrc: string; +}) { + return ( +
+ +
+

{name}

+

{info}

+
+ + Learn More + +
+ ); +} + +export function MembersBackgroundGraphic() { + return ( +
+ + + + + + + + + + + + +
+ ); +} diff --git a/apps/website/src/components/MobileNavigation.tsx b/apps/website/src/components/MobileNavigation.tsx new file mode 100644 index 0000000..53886a9 --- /dev/null +++ b/apps/website/src/components/MobileNavigation.tsx @@ -0,0 +1,146 @@ +import { useEffect, useRef } from "react"; +import { type MouseEvent } from "react"; + +import { + MobileNavigationLink, + MobileNavigationLinkWithRef, +} from "./MobileNavigationLink"; +import clsx from "clsx"; +import type { ToggleType } from "./MobileNavigationToggle"; +import MobileNavigationToggle from "./MobileNavigationToggle"; +import { ColorToggle } from "./ColorToggle"; + +function Overlay({ + isOpen, + closeMenu, +}: { + isOpen: boolean; + closeMenu: () => void; +}) { + function handleClick(event: MouseEvent) { + event.stopPropagation(); + closeMenu(); + } + + return ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions +
+ ); +} + +/* + +Navigation + +hidden and removed from a11y tree when not open + +toggle button can be activated by click or keyboard + +expected behavior is for first menu item to become focused + +menu can close when last link is tabbed away from + +link top current page has aria-current="page" + +escape key should close menu + +*/ + +interface IProps { + isOpen: boolean; + handleNavToggle: (type: ToggleType | undefined) => void; + toggleType: ToggleType; + clearToggleType: () => void; +} + +export function MobileNavigation({ + isOpen, + handleNavToggle, + toggleType, + clearToggleType, +}: IProps) { + const firstElement = useRef(null); + + useEffect(() => { + if (!firstElement.current) { + return; + } + if (isOpen && toggleType === "KEYPRESS") { + firstElement.current.focus(); + clearToggleType(); + } + }, [isOpen, toggleType]); + + return ( + <> +
{ + if (isOpen && event.code === "Tab" && event.shiftKey) { + handleNavToggle("KEYPRESS"); + } + }} + > + +
+ handleNavToggle("CLICK")} isOpen={isOpen} /> + + + + ); +} diff --git a/apps/website/src/components/MobileNavigationLink.tsx b/apps/website/src/components/MobileNavigationLink.tsx new file mode 100644 index 0000000..7c39577 --- /dev/null +++ b/apps/website/src/components/MobileNavigationLink.tsx @@ -0,0 +1,61 @@ +import { forwardRef, type ReactNode, type Ref } from "react"; + +const classes = + "font-redhat font-bold text-xl dark:text-breadgray-grey dark:hover:text-breadgray-white text-breadgray-toast hover:text-breadgray-burnt px-4 py-2"; + +export interface MobileNavigationLinkProps { + children: ReactNode; + href: string; + isExternal?: boolean; + handleNavToggle: (type: "CLICK" | "KEYPRESS") => void; + ref?: Ref; +} + +export function MobileNavigationLink(props: MobileNavigationLinkProps) { + const { children, href, isExternal, handleNavToggle, ref } = props; + + function handleClick() { + console.log("link being clicked"); + handleNavToggle("CLICK"); + } + + if (isExternal) { + return ( + + {children} + + ); + } + + return ( + + {children} + + ); +} + +export const MobileNavigationLinkWithRef = forwardRef< + HTMLAnchorElement, + MobileNavigationLinkProps +>(({ children, href, handleNavToggle }, ref) => { + if (!children) throw new Error("Must pass a child component!"); + + // return null; + // if (!isValidElement(children)) return null; + return ( + handleNavToggle("CLICK")} + > + {children} + + ); +}); diff --git a/apps/website/src/components/MobileNavigationToggle.tsx b/apps/website/src/components/MobileNavigationToggle.tsx new file mode 100644 index 0000000..fd2e53f --- /dev/null +++ b/apps/website/src/components/MobileNavigationToggle.tsx @@ -0,0 +1,69 @@ +import clsx from "clsx"; +import { useRef, useState } from "react"; + +export type ToggleType = "CLICK" | "KEYPRESS" | null; + +function MobileNavigationToggle({ + handleClick, + navIsOpen, +}: { + navIsOpen: boolean; + handleClick: (type: ToggleType) => void; +}) { + const [isKeypress, setIsKeypress] = useState(false); + const buttonRef = useRef(null); + + function handleKeyDown() { + setIsKeypress(true); + } + + function handleClickHere() { + if (isKeypress) { + handleClick("KEYPRESS"); + } else { + buttonRef.current?.blur(); + handleClick("CLICK"); + } + } + return ( + + ); +} + +export default MobileNavigationToggle; diff --git a/apps/website/src/components/TertiaryButtonLink.tsx b/apps/website/src/components/TertiaryButtonLink.tsx new file mode 100644 index 0000000..867ff5a --- /dev/null +++ b/apps/website/src/components/TertiaryButtonLink.tsx @@ -0,0 +1,32 @@ +import type { ReactNode } from "react"; + +export function TertiaryButtonLink({ + children, + href, + isExternal, +}: { + children: ReactNode; + href: string; + isExternal?: boolean; +}) { + const classes = + "rounded-lg inline-block dark:bg-breadgray-600 dark:text-white border-2 border-breadpink-300 font-redhat font-bold text-center px-4 py-2.5 md:px-6 md:py-3 md:text-xl"; + + if (isExternal) + return ( + + {children} + + ); + + return ( + + {children} + + ); +} diff --git a/apps/website/src/components/TertiaryLink.tsx b/apps/website/src/components/TertiaryLink.tsx new file mode 100644 index 0000000..1340dd5 --- /dev/null +++ b/apps/website/src/components/TertiaryLink.tsx @@ -0,0 +1,30 @@ +import type { ReactNode } from "react"; + +interface IProps { + children: ReactNode; + href: string; + isExternal?: boolean; +} + +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 "; + + if (isExternal) { + return ( + + {children} + + ); + } + return ( + + {children} + + ); +} diff --git a/apps/website/src/components/ValuesCard.tsx b/apps/website/src/components/ValuesCard.tsx new file mode 100644 index 0000000..a32f736 --- /dev/null +++ b/apps/website/src/components/ValuesCard.tsx @@ -0,0 +1,100 @@ +export function ValueCard({ + title, + details, + icon, +}: { + title: string; + details: string; + icon: keyof typeof ValueIcons; +}) { + return ( +
+
+
+ {ValueIcons[icon]} +
+

{title}

+

{details}

+
+
+ ); +} + +export const ValueIcons = { + MESSAGE: , + EYE: , + RELOAD: , + VERTICAL_ARROWS: , +}; + +export type ValueIcon = keyof typeof ValueIcons; + +export function MessageIcon() { + return ( + + + + ); +} +export function ReloadIcon() { + return ( + + + + ); +} + +export function VerticalArrowsIcon() { + return ( + + + + ); +} + +export function EyeIcon() { + return ( + + + + + + + + + + + ); +} diff --git a/apps/website/src/components/ValuesSection.tsx b/apps/website/src/components/ValuesSection.tsx new file mode 100644 index 0000000..8d9805c --- /dev/null +++ b/apps/website/src/components/ValuesSection.tsx @@ -0,0 +1,265 @@ +import clsx from "clsx"; + +import { ValueCard, type ValueIcon } from "./ValuesCard"; + +const values: Array<{ + title: string; + details: string; + icon: ValueIcon; +}> = [ + { + title: "Dual Power", + details: + "We must build parallel tech institutions from the grassroots because technology without direct social intervention only favors those in power.", + icon: "VERTICAL_ARROWS", + }, + { + title: "Economic Democracy", + details: + "We strive for an economy in which resources are allocated democratically rather than through market forces.", + icon: "MESSAGE", + }, + { + title: "Mutual Aid", + details: + "We believe in communal solidarity through reciprocal exchange for mutual benefit so everyone can live a dignified life.", + icon: "RELOAD", + }, + { + title: "Transparent Governance", + details: + "Everyone should have a say and those given power by the community must be held responsible for their decisions for all to see.", + icon: "EYE", + }, +]; + +export function ValuesSection() { + return ( +
+
+
+

+ Currency + with a conscience +

+
+ Here are the principles that guide Breadchain projects and their + missions. +
+
+
+
+ +
+ {values.map(({ title, details, icon }, i) => { + return ( + <> + {i !== 0 && ( +
+ + + + + + + + + +
+ )} + + + ); + })} +
+
+
+ ); +} + +export function ValuesBackgroundGraphics() { + return ( +
+ + + +
+ ); +} + +function ValuesLarge({ isRightSide }: { isRightSide: boolean }) { + return ( +
+ + + + + + + + + + + + + + +
+ ); +} + +function ValuesCenter() { + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ ); +} diff --git a/apps/website/src/env.d.ts b/apps/website/src/env.d.ts new file mode 100644 index 0000000..f964fe0 --- /dev/null +++ b/apps/website/src/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/apps/website/src/layouts/Layout.astro b/apps/website/src/layouts/Layout.astro new file mode 100644 index 0000000..3554647 --- /dev/null +++ b/apps/website/src/layouts/Layout.astro @@ -0,0 +1,39 @@ +--- +import "@fontsource/red-hat-text/400.css"; +import "@fontsource/red-hat-text/500.css"; +import "@fontsource/red-hat-text/600.css"; +import "@fontsource/red-hat-text/700.css"; +import "@fontsource/poppins/700.css"; + +import "../styles/index.css"; + +import { Header } from "../components/Header"; +import { Footer } from "../components/Footer"; + +interface Props { + title: string; +} + +const { title } = Astro.props; +--- + + + + + + + + + + {title} + + +
+
+ +
+