ash_hq/lib/ash_hq_web/pages/home.ex

473 lines
19 KiB
Elixir
Raw Normal View History

2022-03-28 10:26:35 +13:00
defmodule AshHqWeb.Pages.Home do
@moduledoc "The home page"
2022-03-28 10:26:35 +13:00
use Surface.LiveComponent
2022-04-02 08:11:17 +13:00
alias AshHqWeb.Components.{CalloutText, CodeExample, SearchBar}
2022-08-31 10:18:34 +12:00
alias Surface.Components.Form
2022-08-31 17:39:22 +12:00
alias Surface.Components.Form.{Field, TextInput, Submit}
import AshHqWeb.Components.CodeExample, only: [to_code: 1]
2022-03-28 10:26:35 +13:00
data(signed_up, :boolean, default: false)
data(email_form, :any)
2022-08-31 10:18:34 +12:00
2022-03-28 10:26:35 +13:00
def render(assigns) do
~F"""
<div class="antialiased">
2022-08-31 20:11:15 +12:00
<div class="my-2 dark:bg-primary-black dark:bg-dark-grid bg-light-grid flex flex-col items-center pt-24">
<div class="text-5xl px-12 font-bold max-w-5xl mx-auto mt-2 text-center">
Build <CalloutText>powerful</CalloutText> and <CalloutText>composable</CalloutText> applications with a <CalloutText>flexible</CalloutText> tool-chain.
2022-03-28 10:26:35 +13:00
</div>
<div class="text-xl font-light text-gray-700 dark:text-gray-400 max-w-4xl mx-auto mt-4 text-center">
A declarative foundation for ambitious applications. Model your domain, derive the rest.
</div>
<div class="flex flex-col space-y-4 md:space-x-4 md:space-y-0 md:flex-row items-center mt-16 mb-10">
2022-08-31 11:25:42 +12:00
<div class="flex items-center h-12 px-4 rounded-lg bg-orange-500 dark:text-white dark:hover:text-gray-200 hover:text-white">
2022-08-31 20:11:15 +12:00
<a href="/docs/guides/ash/latest/tutorials/get-started.md" target="_blank">Get Started</a>
</div>
<SearchBar class="w-80 md:w-96" />
</div>
2022-08-31 20:11:15 +12:00
<div class="max-w-7xl px-4 sm:px-6 md:px-8 mb-8">
<h2 class="mt-8 font-semibold text-red-500 dark:text-red-400">
Write it once
</h2>
<p class="mt-4 text-3xl sm:text-4xl text-slate-900 font-extrabold tracking-tight dark:text-slate-50">
2022-09-02 08:05:20 +12:00
Why do developers keep reinventing the wheel?
2022-08-31 20:11:15 +12:00
</p>
<p class="text-slate-500 dark:text-slate-400 mt-4 max-w-3xl space-y-6">
Every time you start a new app, are you rebuilding features that you've already built many times?
Wouldn't it be great if you could just focus on the important parts of an app without reinventing ways to authenticate, add permissions, etc.
Ash allows you to not only use patterns in existing extensions, it lets you extract your own patterns into custom extensions.
So when you need to do it again in a new application, it's already done. Just wire it up!
</p>
</div>
2022-08-31 20:11:15 +12:00
<div class="max-w-7xl px-4 sm:px-6 md:px-8 mb-8">
<h2 class="mt-8 font-semibold text-red-500 dark:text-red-400">
Consistency
</h2>
<p class="mt-4 text-3xl sm:text-4xl text-slate-900 font-extrabold tracking-tight dark:text-slate-50">
2022-09-03 03:41:01 +12:00
A place for everything and everything in it's place
2022-08-31 20:11:15 +12:00
</p>
<p class="text-slate-500 dark:text-slate-400 mt-4 max-w-3xl space-y-6">
2022-09-03 03:41:01 +12:00
Ash helps keep things neat and organized by providing good patterns for structuring your application.
Over time and with larger teams of different experience levels,
patterns change and drift away from each-other across our applications.
With that said, nothing in Ash depends on what folders or files you put things in, so you are
free to experiment or make the choices that make sense to you.
2022-08-31 20:11:15 +12:00
</p>
<p class="text-slate-500 dark:text-slate-400 mt-4 max-w-3xl space-y-6">
Spaghetti belongs in the kitchen, not in your codebase.
Ash provides the ability to keep all similar parts of your application consistent,
making it easy to share an architectural vision while allowing escape hatches to do something different if needed.
</p>
</div>
2022-08-31 20:11:15 +12:00
<div class="max-w-7xl px-4 sm:px-6 md:px-8 mb-8">
<h2 class="mt-8 font-semibold text-red-500 dark:text-red-400">
Incredibly Powerful
</h2>
<p class="mt-4 text-3xl sm:text-4xl text-slate-900 font-extrabold tracking-tight dark:text-slate-50">
Ash is more than it appears
</p>
<p class="text-slate-500 dark:text-slate-400 mt-4 max-w-3xl space-y-6">
Ash is more than just auto-generated API or an Admin UI.
Its a fully extensible DSL to model your domain, which creates a declarative,
highly introspectable representation. This in turn can be used to derive anything you want.
</p>
<p class="text-slate-500 dark:text-slate-400 mt-4 max-w-3xl space-y-6">
Ash has built in extensions that allow you to generate Admin UIs or Phoenix LiveView Form helpers,
saving a ton of boilerplate. Even going as far as fully swapping data layers, Ash lets you do
something traditionally extremely difficult with ease.
</p>
</div>
2022-08-31 11:47:53 +12:00
2022-09-02 07:56:50 +12:00
<div id="testimonials" class="flex flex-col items-center content-center space-y-8 w-[28rem]">
2022-08-31 20:11:15 +12:00
<div class="w-96 lg:min-w-fit lg:max-w-min bg-gray-200 rounded-xl p-8 md:p-0 dark:bg-gray-800 drop-shadow-xl lg:relative lg:top-16 lg:-left-52">
<div class="pt-6 md:p-8 text-center md:text-left space-y-4">
<p class="text-lg font-light text-gray-700 dark:text-gray-100 break-words">
2022-08-31 11:47:53 +12:00
"Through its declarative extensibility, Ash delivers more than you'd expect: Powerful APIs with filtering/sorting/pagination/calculations/aggregations, pub/sub, authorization, rich introspection, GraphQL... It's what empowers this solo developer to build an ambitious ERP!"
</p>
2022-08-31 20:11:15 +12:00
<p class="font-bold">
<div class="text-orange-500 dark:text-orange-400">
Frank Dugan III
</div>
<div class="text-gray-700 dark:text-gray-300">
System Specialist, SunnyCor Inc.
</div>
</p>
</div>
2022-08-31 11:47:53 +12:00
</div>
2022-08-31 20:11:15 +12:00
<div class="w-96 lg:min-w-fit lg:max-w-min bg-gray-100 rounded-xl p-8 md:p-0 dark:bg-gray-700 bg-white drop-shadow-xl lg:relative lg:-top-16 lg:-right-52">
<div class="pt-6 md:p-8 text-center md:text-left space-y-4">
<p class="text-lg font-light text-gray-700 dark:text-gray-100 break-words">
2022-08-31 11:47:53 +12:00
"What stood out to me was how incredibly easy Ash made it for me to go from a proof of concept, to a working prototype using ETS, to a live app using Postgres."
</p>
2022-08-31 20:11:15 +12:00
<p class="font-bold">
<div class="text-orange-500 dark:text-orange-400">
Brett Kolodny
</div>
<div class="text-gray-700 dark:text-gray-300">
Full stack engineer, MEW
</div>
</p>
</div>
2022-08-31 11:47:53 +12:00
</div>
2022-08-31 20:11:15 +12:00
<div class="w-96 lg:min-w-fit lg:max-w-min bg-gray-200 rounded-xl p-8 md:p-0 dark:bg-gray-800 bg-white drop-shadow-xl lg:relative lg:-top-32 lg:-left-64">
<div class="pt-6 md:p-8 text-center md:text-left space-y-4">
<p class="text-lg font-light text-gray-700 dark:text-gray-100 break-words">
2022-08-31 11:47:53 +12:00
"Ash is such powerful idea and it gives Alembic such a massive competitive advantage that Id be really stupid to tell anyone about it."
</p>
2022-08-31 20:11:15 +12:00
<p class="font-bold">
<div class="text-orange-500 dark:text-orange-400">
Josh Price
</div>
<div class="text-gray-700 dark:text-gray-300">
Technical Director, Alembic
</div>
</p>
</div>
2022-08-31 11:47:53 +12:00
</div>
2022-08-31 20:11:15 +12:00
<div class="w-96 lg:min-w-fit lg:max-w-min bg-gray-100 rounded-xl p-8 md:p-0 dark:bg-gray-700 drop-shadow-xl lg:relative lg:-top-64 lg:-right-32">
<div class="pt-6 md:p-8 text-center md:text-left space-y-4">
<p class="text-lg font-light text-gray-700 dark:text-gray-100 break-words">
2022-08-31 20:11:15 +12:00
"Ash Framework enabled us to build a robust platform for delivering financial services using bitcoin. Ash proved itself to our team by handling innovative use cases with ease and it continues to evolve ahead of our growing list of needs."
</p>
2022-08-31 20:11:15 +12:00
<p class="font-bold">
<div class="text-orange-500 dark:text-orange-400">
Yousef Janajri
</div>
<div class="text-gray-700 dark:text-gray-300">
CTO & Co-Founder, Coinbits
</div>
</p>
</div>
</div>
</div>
2022-08-31 11:47:53 +12:00
2022-09-01 04:37:02 +12:00
<div class="flex flex-col items-center my-10 space-y-4 hidden sm:block">
2022-08-31 11:25:42 +12:00
{#if @signed_up}
Thank you for joining our mailing list!
{#else}
<p class="text-2xl font-medium text-gray-700 dark:text-gray-200 max-w-4xl mx-auto text-center">Join our mailing list for (tastefully paced) updates!</p>
2022-08-31 20:11:15 +12:00
<Form
for={@email_form}
change="validate_email_form"
submit="submit_email_form"
class="flex flex-col md:flex-row space-x-4 space-y-4 items-center"
>
2022-08-31 11:25:42 +12:00
<Field name={:email}>
2022-08-31 20:11:15 +12:00
<TextInput
class="w-80 md:w-96 button border border-gray-400 bg-gray-200 dark:border-gray-700 rounded-lg dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600"
opts={placeholder: "user@email.com"}
/>
2022-08-31 11:25:42 +12:00
</Field>
<Submit class="w-fit flex items-center px-4 rounded-lg bg-orange-500 dark:text-white dark:hover:text-gray-200 hover:text-white h-10">Join</Submit>
2022-08-31 11:25:42 +12:00
</Form>
{/if}
</div>
2022-09-01 04:37:02 +12:00
<div class="max-w-7xl px-4 sm:px-6 md:px-8 my-8 hidden sm:block">
<h2 class="mt-8 font-semibold text-red-500 dark:text-red-400">
Simple declarative DSL
</h2>
2022-08-31 20:11:15 +12:00
<p class="mt-4 text-3xl sm:text-4xl text-slate-900 font-extrabold tracking-tight dark:text-slate-50">
A taste of how to configure Ash
</p>
<p class="text-slate-500 dark:text-slate-400 mt-4 max-w-3xl space-y-6">
2022-08-31 20:11:15 +12:00
Below are some examples of the way you can model your resources with actions, attributes and relationships.
You can easily swap data layers between Postgres or ETS for example, or add your own data layer extension.
Once you've modelled your resources, you can derive GraphQL or JSON API external APIs from them.
</p>
2022-03-28 10:26:35 +13:00
</div>
2022-04-08 18:59:39 +12:00
2022-04-02 08:11:17 +13:00
<div class="pt-6 pb-6 w-full hidden sm:block mt-36">
2022-03-30 05:12:28 +13:00
<div class="grid grid-cols-1 lg:grid-cols-2 gap-10">
2022-03-28 10:26:35 +13:00
<CodeExample
id="define-a-resource"
2022-03-29 08:47:43 +13:00
class="grow min-w-fit max-w-[1000px]"
code={post_example()}
2022-03-28 10:26:35 +13:00
title="Define a resource"
/>
<div class="flex flex-col space-y-8">
<CodeExample
class="w-auto"
2022-03-29 08:47:43 +13:00
collapsible
id="use-it-programmatically"
code={changeset_example()}
2022-03-29 08:47:43 +13:00
title="Use it programmatically"
2022-03-28 10:26:35 +13:00
/>
<CodeExample
class="w-auto"
2022-03-29 08:47:43 +13:00
collapsible
2022-03-28 10:26:35 +13:00
id="graphql-interface"
code={graphql_example()}
2022-03-28 10:26:35 +13:00
title="Add a GraphQL interface"
/>
<CodeExample
class="w-auto"
2022-03-29 08:47:43 +13:00
collapsible
2022-03-28 10:26:35 +13:00
start_collapsed
id="authorization-policies"
code={policies_example()}
2022-03-28 10:26:35 +13:00
title="Add authorization policies"
/>
<CodeExample
class="w-auto"
2022-03-29 08:47:43 +13:00
collapsible
2022-03-28 10:26:35 +13:00
start_collapsed
id="aggregates"
code={aggregate_example()}
2022-03-28 10:26:35 +13:00
title="Define aggregates and calculations"
/>
<CodeExample
class="w-auto"
2022-03-29 08:47:43 +13:00
collapsible
2022-03-28 10:26:35 +13:00
start_collapsed
id="pubsub"
code={notifier_example()}
2022-03-28 10:26:35 +13:00
title="Broadcast changes over Phoenix PubSub"
/>
<CodeExample
class="w-auto"
2022-03-29 08:47:43 +13:00
collapsible
2022-03-28 10:26:35 +13:00
start_collapsed
id="live-view"
code={live_view_example()}
2022-03-28 10:26:35 +13:00
title="Use it with Phoenix LiveView"
/>
</div>
</div>
</div>
2022-08-31 11:25:42 +12:00
<div class="flex flex-col items-center my-10 space-y-4">
2022-08-31 11:25:42 +12:00
{#if @signed_up}
Thank you for joining our mailing list!
{#else}
<p class="text-2xl font-medium text-gray-700 dark:text-gray-200 max-w-4xl mx-auto text-center">Join our mailing list for (tastefully paced) updates!</p>
2022-08-31 20:11:15 +12:00
<Form
for={@email_form}
change="validate_email_form"
submit="submit_email_form"
class="flex flex-col md:flex-row space-x-4 space-y-4 items-center"
>
2022-08-31 11:25:42 +12:00
<Field name={:email}>
2022-08-31 20:11:15 +12:00
<TextInput
class="w-80 md:w-96 button border border-gray-400 bg-gray-200 dark:border-gray-700 rounded-lg dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600"
opts={placeholder: "user@email.com"}
/>
2022-08-31 11:25:42 +12:00
</Field>
<Submit class="w-fit flex items-center px-4 rounded-lg bg-orange-500 dark:text-white dark:hover:text-gray-200 hover:text-white h-10">Join</Submit>
2022-08-31 11:25:42 +12:00
</Form>
{/if}
</div>
2022-03-28 10:26:35 +13:00
</div>
</div>
"""
end
2022-08-31 10:18:34 +12:00
def mount(socket) do
{:ok,
assign(
socket,
signed_up: false,
email_form:
AshPhoenix.Form.for_create(AshHq.MailingList.Email, :create,
api: AshHq.MailingList,
upsert?: true,
upsert_identity: :unique_email
)
)}
end
def handle_event("validate_email_form", %{"form" => form}, socket) do
{:noreply,
assign(socket, email_form: AshPhoenix.Form.validate(socket.assigns.email_form, form))}
end
def handle_event("submit_email_form", _, socket) do
case AshPhoenix.Form.submit(socket.assigns.email_form) do
{:ok, _} ->
{:noreply, assign(socket, :signed_up, true)}
{:error, form} ->
{:noreply, assign(socket, email_form: form)}
end
end
@changeset_example """
post = Example.Post.create!(%{
text: "Declarative programming is fun!"
})
2022-03-28 10:26:35 +13:00
Example.Post.react!(post, %{type: :like})
2022-03-28 10:26:35 +13:00
Example.Post
|> Ash.Query.filter(likes > 10)
|> Ash.Query.sort(likes: :desc)
|> Example.read!()
"""
|> to_code()
defp changeset_example do
@changeset_example
2022-03-28 10:26:35 +13:00
end
@live_view_example """
def mount(_params, _session, socket) do
form = AshPhoenix.Form.for_create(Example.Post, :create)
2022-03-28 10:26:35 +13:00
{:ok, assign(socket, :form, form}}
end
2022-03-28 10:26:35 +13:00
def handle_event("validate", %{"form" => input}, socket) do
form = AshPhoenix.Form.validate(socket.assigns.form, input)
2022-03-28 10:26:35 +13:00
{:ok, assign(socket, :form, form)}
end
2022-03-28 10:26:35 +13:00
def handle_event("submit", _, socket) do
case AshPhoenix.Form.submit(socket.assigns.form) do
{:ok, post} ->
{:ok, redirect_to_post(socket, post)}
2022-03-28 10:26:35 +13:00
{:error, form_with_errors} ->
{:noreply, assign(socket, :form, form_with_errors)}
end
end
"""
|> to_code()
defp live_view_example do
@live_view_example
2022-03-28 10:26:35 +13:00
end
@graphql_example """
graphql do
type :post
queries do
get :get_post, :read
list :feed, :read
end
mutations do
create :create_post, :create
update :react_to_post, :react
end
end
"""
|> to_code()
defp graphql_example do
@graphql_example
2022-03-28 10:26:35 +13:00
end
@policies_example """
policies do
policy action_type(:read) do
authorize_if expr(visibility == :everyone)
authorize_if relates_to_actor_via([:author, :friends])
end
end
"""
|> to_code()
defp policies_example do
@policies_example
2022-03-28 10:26:35 +13:00
end
@notifier_example """
pub_sub do
module ExampleEndpoint
prefix "post"
publish_all :create, ["created"]
publish :react, ["reaction", :id] event: "reaction"
end
"""
|> to_code()
defp notifier_example do
@notifier_example
2022-03-28 10:26:35 +13:00
end
@aggregate_example """
aggregates do
count :likes, :reactions do
filter expr(type == :like)
end
count :dislikes, :reactions do
filter expr(type == :dislike)
end
end
calculations do
calculate :like_ratio, :float do
expr(likes / (likes + dislikes))
end
end
"""
|> to_code()
defp aggregate_example do
@aggregate_example
2022-03-28 10:26:35 +13:00
end
@post_example """
defmodule Example.Post do
use AshHq.Resource,
data_layer: AshPostgres.DataLayer
postgres do
table "posts"
repo Example.Repo
end
attributes do
attribute :text, :string do
allow_nil? false
end
attribute :visibility, :atom do
constraints [
one_of: [:friends, :everyone]
]
end
end
actions do
update :react do
argument :type, Example.Types.ReactionType do
allow_nil? false
end
change manage_relationship(
:type,
:reactions,
type: :append
)
end
end
relationships do
belongs_to :author, Example.User do
required? true
end
has_many :reactions, Example.Reaction
end
end
"""
|> to_code()
defp post_example do
@post_example
2022-03-28 10:26:35 +13:00
end
end