Skip to content

Commit

Permalink
Merge pull request #78 from Etherna/feature/EWEB-90-antihill-page
Browse files Browse the repository at this point in the history
Feature/eweb 90 antihill page
  • Loading branch information
mattiaz9 authored Nov 4, 2024
2 parents ac30c55 + e3123ec commit 80072f5
Show file tree
Hide file tree
Showing 17 changed files with 1,961 additions and 1,992 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ jobs:
env:
PUBLIC_SITE_URL: "https://info.etherna.io"
PUBLIC_DIRECTUS_URL: "https://cms.etherna.io"
PUBLIC_MAILCHIMP_AUDIENCE_ID: "474e245439"
PUBLIC_MAILCHIMP_PRODUCT_AUDIENCE_ID: "bf5f1dc2ea"
ANALYTICS_URL: "https://analytics.etherna.io"
ANALYTICS_SITE_ID: "1"

Expand Down
2 changes: 2 additions & 0 deletions apps/cms/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@directus/constants": "12.0.0",
"@directus/sdk": "17.0.1",
"@directus/types": "12.0.1",
"@mailchimp/mailchimp_marketing": "3.0.80",
"@octokit/rest": "21.0.2",
"@sendgrid/mail": "8.1.3",
"axios": "1.7.7",
Expand All @@ -45,6 +46,7 @@
"@rollup/plugin-typescript": "11.1.6",
"@rollup/plugin-url": "8.0.2",
"@types/express": "4.17.21",
"@types/mailchimp__mailchimp_marketing": "3.0.20",
"@vitejs/plugin-vue": "5.1.3",
"@vue/compiler-sfc": "3.5.2",
"concurrently": "8.2.2",
Expand Down
4 changes: 3 additions & 1 deletion apps/cms/scripts/entries.extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ export function getExtensionsEntries() {
format,
exports,
},
external: isApiExtension ? API_SHARED_DEPS : APP_SHARED_DEPS,
external: isApiExtension
? [...API_SHARED_DEPS, "@mailchimp/mailchimp_marketing"]
: APP_SHARED_DEPS,
plugins: isApiExtension ? API_PLUGINS : APP_PLUGINS,
logLevel: "warn",
onwarn: (warning, warn) => {
Expand Down
2 changes: 2 additions & 0 deletions apps/cms/src/deploys/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { defineEndpoint } from "@directus/extensions"
import { Octokit } from "@octokit/rest"

require("dotenv").config()

export default defineEndpoint({
id: "deploys",
handler(router, context) {
Expand Down
5 changes: 5 additions & 0 deletions apps/cms/src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@
"name": "deploys",
"source": "./deploys/index.ts"
},
{
"type": "endpoint",
"name": "subscribe",
"source": "./subscribe/index.ts"
},
{
"type": "migrations",
"source": "./migrations"
Expand Down
75 changes: 75 additions & 0 deletions apps/cms/src/subscribe/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { defineEndpoint } from "@directus/extensions"
import mailchimp from "@mailchimp/mailchimp_marketing"
import axios, { isAxiosError } from "axios"

require("dotenv").config()

export default defineEndpoint({
id: "subscribe",
handler(router, context) {
router.post("/:list", async (req, res) => {
try {
mailchimp.setConfig({
apiKey: process.env.MAILCHIMP_TOKEN,
server: process.env.MAILCHIMP_SERVER,
})

const submission = req.body as {
email: string
fname: string
lname?: string
mailchimpTags?: string
}

const listId = req.params.list
const tags = (submission.mailchimpTags ?? "")
.split(",")
.map((tag) => tag.trim())
.filter((tag) => tag)
const email = submission.email

const member = {
email_address: email,
status: "pending",
tags,
merge_fields: {
FNAME: submission.fname,
LNAME: submission.lname ?? "",
},
} satisfies mailchimp.lists.AddListMemberBody

const url = `https://${process.env.MAILCHIMP_SERVER}.api.mailchimp.com/3.0/lists/${listId}/members`
const resp = await axios.post(
url,
{
email_address: email,
status: "pending",
tags,
merge_fields: {
FNAME: submission.fname,
LNAME: submission.lname ?? "",
},
},
{
headers: {
Authorization: `Bearer ${process.env.MAILCHIMP_TOKEN}`,
"Content-Type": "application/json",
},
},
)

res.json({ status: "ok", code: "DONE" })
} catch (err) {
if (isAxiosError(err) && err.response?.data.title === "Member Exists") {
res.status(200).json({ status: "ok", code: "EXIST" })
return
}

const error = isAxiosError(err) ? err.response?.data : err
console.error(error)

res.status(400).json({ status: "error", error })
}
})
},
})
2 changes: 1 addition & 1 deletion apps/web/.astro/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"_variables": {
"lastUpdateCheck": 1725632250401
"lastUpdateCheck": 1730650544720
}
}
2 changes: 2 additions & 0 deletions apps/web/.env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
PUBLIC_SITE_URL="http://localhost:3000"
PUBLIC_DIRECTUS_URL="http://localhost:8055"
PUBLIC_MAILCHIMP_AUDIENCE_ID="xxxxxxxxx"
PUBLIC_MAILCHIMP_PRODUCT_AUDIENCE_ID="xxxxxxxxx"
ANALYTICS_URL="https://analytics.etherna.test"
ANALYTICS_SITE_ID="1"
Binary file added apps/web/src/assets/anthill.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions apps/web/src/components/common/alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function Alert({ children, className, type = "success", title, onClose }:
return (
<div
className={cn(
"block w-full rounded border px-4 py-3",
"block w-full rounded border px-4 py-3 text-left",
{
"border-green-200 bg-green-100 text-green-600": type === "success",
"border-red-200 bg-red-100 text-red-600": type === "danger",
Expand All @@ -39,7 +39,7 @@ export function Alert({ children, className, type = "success", title, onClose }:
</button>
)}
</div>
<span>{children}</span>
<span className="">{children}</span>
</div>
)
}
12 changes: 8 additions & 4 deletions apps/web/src/components/landing/newsletter-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { SpinnerLight } from "@/components/assets/animated"
import { Alert } from "@/components/common/alert"
import { Button } from "@/components/common/button"
import { cn } from "@/utils/classnames"
import { userLocale } from "@/utils/lang"
import { routes } from "@/utils/routes"
import { validateEmail } from "@/utils/validation"

Expand Down Expand Up @@ -35,12 +36,12 @@ export function NewsletterForm() {
return
}

const apiEndpoint = `${import.meta.env.DIRECTUS_URL}/${
import.meta.env.DIRECTUS_PROJECT
}/custom/newsletter`
const audienceId = import.meta.env.PUBLIC_MAILCHIMP_AUDIENCE_ID
const apiEndpoint = `${import.meta.env.PUBLIC_DIRECTUS_URL}/subscribe/${audienceId}`
await axios.post(apiEndpoint, {
email,
firstName,
fname: firstName,
mailchimpTags: `website,${userLocale()}`,
})

sessionStorage.setItem("subscriber:email", email)
Expand All @@ -64,12 +65,15 @@ export function NewsletterForm() {
className="rounded-t-lg focus:rounded-t-lg lg:rounded-none lg:rounded-l-lg lg:focus:rounded-none lg:focus:rounded-l-lg"
placeholder={t("namePlaceholder")}
value={firstName}
autoComplete="name"
onChange={e => setFirstName(e.target.value)}
/>
<NewsletterFormField
type="email"
className="-mt-px lg:mt-0"
placeholder={t("emailPlaceholder")}
autoComplete="email"
inputMode="email"
value={email}
onChange={e => setEmail(e.target.value)}
/>
Expand Down
126 changes: 126 additions & 0 deletions apps/web/src/components/landing/product-subscription-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { useState } from "react"
import { useTranslation } from "react-i18next"
import axios from "axios"

import { SpinnerLight } from "@/components/assets/animated"

import { Alert } from "@/components/common/alert"
import { Button } from "@/components/common/button"
import { cn } from "@/utils/classnames"
import { userLocale } from "@/utils/lang"
import { routes } from "@/utils/routes"
import { validateEmail } from "@/utils/validation"

import type { InputHTMLAttributes } from "react"

export function ProductSubscriptionForm() {
const [firstName, setFirstName] = useState("")
const [email, setEmail] = useState("")
const [isSubmitting, setIsSubmitting] = useState(false)
const [error, setError] = useState<string>()
const [isSuccess, setIsSuccess] = useState(false)
const { t } = useTranslation("landing")

const sendFormRequest = async () => {
setIsSubmitting(true)

try {
// fields validation
if (firstName.length < 2) {
setIsSubmitting(false)
setError(t("subcribeErrorName"))
return
}
if (email.length === 0 || !validateEmail(email)) {
setIsSubmitting(false)
setError(t("subcribeErrorEmail"))
return
}

const audienceId = import.meta.env.PUBLIC_MAILCHIMP_PRODUCT_AUDIENCE_ID
const apiEndpoint = `${import.meta.env.PUBLIC_DIRECTUS_URL}/subscribe/${audienceId}`
await axios.post(apiEndpoint, {
email,
fname: firstName,
mailchimpTags: `anthill,website,${userLocale()}`,
})

sessionStorage.setItem("subscriber:email", email)

setError(undefined)
setIsSubmitting(false)
setIsSuccess(true)
setEmail("")
setFirstName("")
} catch (err) {
console.error(err)
setError(t("subcribeErrorDescription"))
setIsSubmitting(false)
}
}

return (
<>
<form className="flex w-full flex-col gap-4">
<NewsletterFormField
type="text"
className="w-full rounded-md lg:max-w-full"
placeholder={t("namePlaceholder")}
autoComplete="name"
value={firstName}
onChange={e => setFirstName(e.target.value)}
/>
<NewsletterFormField
type="email"
className="w-full rounded-md lg:max-w-full"
placeholder={t("emailPlaceholder")}
autoComplete="email"
inputMode="email"
value={email}
onChange={e => setEmail(e.target.value)}
/>

<Button
className="h-10 w-full justify-center text-center"
type="primary"
disabled={isSubmitting}
onClick={sendFormRequest}
>
{isSubmitting && <SpinnerLight className="mr-2 h-6 w-6" />}
Subscribe
</Button>
</form>

{error && (
<div className="my-4 w-full max-w-4xl">
<Alert type="danger" title={t("subcribeErrorTitle")} onClose={() => setError(undefined)}>
{error}
</Alert>
</div>
)}

{isSuccess && (
<div className="my-4 w-full max-w-4xl">
<Alert type="success" title="Almost done!">
Thank you for your submission. Please check your inbox for a confirmation email.
</Alert>
</div>
)}
</>
)
}

function NewsletterFormField({ className, ...props }: InputHTMLAttributes<HTMLInputElement>) {
return (
<input
{...props}
className={cn(
"z-0 w-full rounded-none border border-gray-300 bg-transparent px-4 py-3 transition duration-200 ",
"text-sm placeholder:text-sm placeholder:text-gray-500",
"focus:z-10 focus:border-primary-500 focus:bg-white focus:outline-none",
"lg:-mr-px lg:max-w-64 lg:border-b lg:focus:border-b",
className
)}
/>
)
}
60 changes: 60 additions & 0 deletions apps/web/src/pages/anthill.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
import { Image as AstroImg } from "astro:assets"
import gatewayImg from "@/assets/anthill.png"
import Layout from "@/astro/layout/layout.astro"
import { ProductSubscriptionForm } from "@/components/landing/product-subscription-form"
---

<Layout title="Etherna Gateway" description="A game changer for cloud storage" lang="en">
<div class="container flex flex-col items-center gap-y-12 py-12 md:flex-row-reverse">
<AstroImg src={gatewayImg} alt="Etherna Gateway" class="w-full max-w-64 xl:max-w-80" />
<div class="flex flex-1 flex-col items-center md:items-start">
<h1 class="text-3xl font-extrabold text-gradient md:text-4xl lg:text-5xl">Etherna Anthill</h1>
<p
class="max-w-screen-sm text-center text-base font-medium text-gray-500 md:text-left lg:text-lg"
>
A drop-in replacement for AWS S3, Cloudflare R2 or any other cloud object storage
available... And of course, it's decentralized!
</p>
</div>
</div>

<div class="bg-white py-20">
<ul class="container grid grid-cols-1 gap-4 xs:grid-cols-2 sm:grid-cols-3 sm:gap-8">
<li class="flex flex-col">
<h3 class="mb-1 text-base">DDOS resistant</h3>
<p class="text-sm text-gray-400">
A decentralized storage is a resistent one, as it is not a single point of failure
</p>
</li>
<li class="flex flex-col">
<h3 class="mb-1 text-base">Best compatibility</h3>
<p class="text-sm text-gray-400">
Anthill is compatible with any S3-compatible client, making it easy to integrate
</p>
</li>
<li class="flex flex-col">
<h3 class="mb-1 text-base">Fast</h3>
<p class="text-sm text-gray-400">
Data is retrieved from the nearest node, reducing latency and increasing speed
</p>
</li>
</ul>
</div>

<div class="bg-gradient-to-b from-white to-gray-200 py-20">
<div class="container flex flex-col items-center text-center">
<h2>Subscribe for future updates</h2>
<p class="max-w-sm text-sm text-gray-500">
Anthill is still under development. Subscribe to our newsletter to get updates on our
progress.
</p>

<div class="mt-12 w-full max-w-sm md:mt-20">
<ProductSubscriptionForm client:idle />
</div>
</div>
</div>
</Layout>
Loading

0 comments on commit 80072f5

Please sign in to comment.