Add base marketing
This commit is contained in:
parent
4e6dcd0c7b
commit
9ebc9ab342
0
assets/css/app.css
Normal file
0
assets/css/app.css
Normal file
@ -36,7 +36,7 @@ config :esbuild,
|
|||||||
version: "0.17.11",
|
version: "0.17.11",
|
||||||
send_it: [
|
send_it: [
|
||||||
args:
|
args:
|
||||||
~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
|
~w(js/app.js css/app.css --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
|
||||||
cd: Path.expand("../assets", __DIR__),
|
cd: Path.expand("../assets", __DIR__),
|
||||||
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
|
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
|
||||||
]
|
]
|
||||||
|
|||||||
199
lib/send_it/marketing.ex
Normal file
199
lib/send_it/marketing.ex
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
defmodule SendIt.Marketing do
|
||||||
|
@moduledoc """
|
||||||
|
The Marketing context.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import Ecto.Query, warn: false
|
||||||
|
alias SendIt.Repo
|
||||||
|
|
||||||
|
alias SendIt.Marketing.Contact
|
||||||
|
alias SendIt.Marketing.Message
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the list of contacts.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> list_contacts()
|
||||||
|
[%Contact{}, ...]
|
||||||
|
|
||||||
|
"""
|
||||||
|
def list_contacts do
|
||||||
|
Repo.all(Contact)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets a single contact.
|
||||||
|
|
||||||
|
Raises `Ecto.NoResultsError` if the Contact does not exist.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> get_contact!(123)
|
||||||
|
%Contact{}
|
||||||
|
|
||||||
|
iex> get_contact!(456)
|
||||||
|
** (Ecto.NoResultsError)
|
||||||
|
|
||||||
|
"""
|
||||||
|
def get_contact!(id), do: Repo.get!(Contact, id)
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Creates a contact.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> create_contact(%{field: value})
|
||||||
|
{:ok, %Contact{}}
|
||||||
|
|
||||||
|
iex> create_contact(%{field: bad_value})
|
||||||
|
{:error, %Ecto.Changeset{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
def create_contact(attrs \\ %{}) do
|
||||||
|
%Contact{}
|
||||||
|
|> Contact.changeset(attrs)
|
||||||
|
|> Repo.insert()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Updates a contact.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> update_contact(contact, %{field: new_value})
|
||||||
|
{:ok, %Contact{}}
|
||||||
|
|
||||||
|
iex> update_contact(contact, %{field: bad_value})
|
||||||
|
{:error, %Ecto.Changeset{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
def update_contact(%Contact{} = contact, attrs) do
|
||||||
|
contact
|
||||||
|
|> Contact.changeset(attrs)
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Deletes a contact.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> delete_contact(contact)
|
||||||
|
{:ok, %Contact{}}
|
||||||
|
|
||||||
|
iex> delete_contact(contact)
|
||||||
|
{:error, %Ecto.Changeset{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
def delete_contact(%Contact{} = contact) do
|
||||||
|
Repo.delete(contact)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns an `%Ecto.Changeset{}` for tracking contact changes.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> change_contact(contact)
|
||||||
|
%Ecto.Changeset{data: %Contact{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
def change_contact(%Contact{} = contact, attrs \\ %{}) do
|
||||||
|
Contact.changeset(contact, attrs)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the list of messages.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> list_messages()
|
||||||
|
[%Message{}, ...]
|
||||||
|
|
||||||
|
"""
|
||||||
|
def list_messages do
|
||||||
|
Repo.all(Message)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets a single message.
|
||||||
|
|
||||||
|
Raises `Ecto.NoResultsError` if the Message does not exist.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> get_message!(123)
|
||||||
|
%Message{}
|
||||||
|
|
||||||
|
iex> get_message!(456)
|
||||||
|
** (Ecto.NoResultsError)
|
||||||
|
|
||||||
|
"""
|
||||||
|
def get_message!(id), do: Repo.get!(Message, id)
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Creates a message.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> create_message(%{field: value})
|
||||||
|
{:ok, %Message{}}
|
||||||
|
|
||||||
|
iex> create_message(%{field: bad_value})
|
||||||
|
{:error, %Ecto.Changeset{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
def create_message(attrs \\ %{}) do
|
||||||
|
%Message{}
|
||||||
|
|> Message.changeset(attrs)
|
||||||
|
|> Repo.insert()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Updates a message.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> update_message(message, %{field: new_value})
|
||||||
|
{:ok, %Message{}}
|
||||||
|
|
||||||
|
iex> update_message(message, %{field: bad_value})
|
||||||
|
{:error, %Ecto.Changeset{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
def update_message(%Message{} = message, attrs) do
|
||||||
|
message
|
||||||
|
|> Message.changeset(attrs)
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Deletes a message.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> delete_message(message)
|
||||||
|
{:ok, %Message{}}
|
||||||
|
|
||||||
|
iex> delete_message(message)
|
||||||
|
{:error, %Ecto.Changeset{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
def delete_message(%Message{} = message) do
|
||||||
|
Repo.delete(message)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns an `%Ecto.Changeset{}` for tracking message changes.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> change_message(message)
|
||||||
|
%Ecto.Changeset{data: %Message{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
def change_message(%Message{} = message, attrs \\ %{}) do
|
||||||
|
Message.changeset(message, attrs)
|
||||||
|
end
|
||||||
|
end
|
||||||
22
lib/send_it/marketing/contact.ex
Normal file
22
lib/send_it/marketing/contact.ex
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
defmodule SendIt.Marketing.Contact do
|
||||||
|
use Ecto.Schema
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
schema "contacts" do
|
||||||
|
field :name, :string
|
||||||
|
field :email, :string
|
||||||
|
field :subscribed, :boolean, default: true
|
||||||
|
|
||||||
|
many_to_many :messages, SendIt.Marketing.Message, join_through: "contacts_messages"
|
||||||
|
|
||||||
|
timestamps(type: :utc_datetime)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def changeset(contact, attrs) do
|
||||||
|
contact
|
||||||
|
|> cast(attrs, [:name, :email, :subscribed])
|
||||||
|
|> validate_required([:name, :email, :subscribed])
|
||||||
|
|> unique_constraint(:email)
|
||||||
|
end
|
||||||
|
end
|
||||||
20
lib/send_it/marketing/message.ex
Normal file
20
lib/send_it/marketing/message.ex
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
defmodule SendIt.Marketing.Message do
|
||||||
|
use Ecto.Schema
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
schema "messages" do
|
||||||
|
field :subject, :string
|
||||||
|
field :content, :string
|
||||||
|
|
||||||
|
many_to_many :contacts, SendIt.Marketing.Contact, join_through: "contacts_messages"
|
||||||
|
|
||||||
|
timestamps(type: :utc_datetime)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def changeset(message, attrs) do
|
||||||
|
message
|
||||||
|
|> cast(attrs, [:subject, :content])
|
||||||
|
|> validate_required([:subject, :content])
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -156,8 +156,7 @@ defmodule SendItWeb.CoreComponents do
|
|||||||
phx-connected={hide("#client-error")}
|
phx-connected={hide("#client-error")}
|
||||||
hidden
|
hidden
|
||||||
>
|
>
|
||||||
Attempting to reconnect
|
Attempting to reconnect <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
|
||||||
<.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
|
|
||||||
</.flash>
|
</.flash>
|
||||||
|
|
||||||
<.flash
|
<.flash
|
||||||
|
|||||||
@ -1,32 +1,4 @@
|
|||||||
<header class="px-4 sm:px-6 lg:px-8">
|
<main>
|
||||||
<div class="flex items-center justify-between border-b border-zinc-100 py-3 text-sm">
|
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<a href="/">
|
|
||||||
<img src={~p"/images/logo.svg"} width="36" />
|
|
||||||
</a>
|
|
||||||
<p class="bg-brand/5 text-brand rounded-full px-2 font-medium leading-6">
|
|
||||||
v<%= Application.spec(:phoenix, :vsn) %>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-4 font-semibold leading-6 text-zinc-900">
|
|
||||||
<a href="https://twitter.com/elixirphoenix" class="hover:text-zinc-700">
|
|
||||||
@elixirphoenix
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/phoenixframework/phoenix" class="hover:text-zinc-700">
|
|
||||||
GitHub
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://hexdocs.pm/phoenix/overview.html"
|
|
||||||
class="rounded-lg bg-zinc-100 px-2 py-1 hover:bg-zinc-200/80"
|
|
||||||
>
|
|
||||||
Get Started <span aria-hidden="true">→</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<main class="px-4 py-20 sm:px-6 lg:px-8">
|
|
||||||
<div class="mx-auto max-w-2xl">
|
|
||||||
<.flash_group flash={@flash} />
|
<.flash_group flash={@flash} />
|
||||||
<%= @inner_content %>
|
<%= @inner_content %>
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" class="[scrollbar-gutter:stable]">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
@ -7,47 +7,34 @@
|
|||||||
<.live_title suffix=" · Phoenix Framework">
|
<.live_title suffix=" · Phoenix Framework">
|
||||||
<%= assigns[:page_title] || "SendIt" %>
|
<%= assigns[:page_title] || "SendIt" %>
|
||||||
</.live_title>
|
</.live_title>
|
||||||
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
|
<link phx-track-static rel="stylesheet" href={~p"/assets/css/app.css"} />
|
||||||
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
|
<script defer phx-track-static type="text/javascript" src={~p"/assets/js/app.js"}>
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-white">
|
<body>
|
||||||
<ul class="relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end">
|
<ul>
|
||||||
<%= if @current_user do %>
|
<%= if @current_user do %>
|
||||||
<li class="text-[0.8125rem] leading-6 text-zinc-900">
|
<li>
|
||||||
<%= @current_user.email %>
|
<%= @current_user.email %>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<.link
|
<.link href={~p"/users/settings"}>
|
||||||
href={~p"/users/settings"}
|
|
||||||
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
|
|
||||||
>
|
|
||||||
Settings
|
Settings
|
||||||
</.link>
|
</.link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<.link
|
<.link href={~p"/users/log_out"} method="delete">
|
||||||
href={~p"/users/log_out"}
|
|
||||||
method="delete"
|
|
||||||
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
|
|
||||||
>
|
|
||||||
Log out
|
Log out
|
||||||
</.link>
|
</.link>
|
||||||
</li>
|
</li>
|
||||||
<% else %>
|
<% else %>
|
||||||
<li>
|
<li>
|
||||||
<.link
|
<.link href={~p"/users/register"}>
|
||||||
href={~p"/users/register"}
|
|
||||||
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
|
|
||||||
>
|
|
||||||
Register
|
Register
|
||||||
</.link>
|
</.link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<.link
|
<.link href={~p"/users/log_in"}>
|
||||||
href={~p"/users/log_in"}
|
|
||||||
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
|
|
||||||
>
|
|
||||||
Log in
|
Log in
|
||||||
</.link>
|
</.link>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -2,8 +2,6 @@ defmodule SendItWeb.PageController do
|
|||||||
use SendItWeb, :controller
|
use SendItWeb, :controller
|
||||||
|
|
||||||
def home(conn, _params) do
|
def home(conn, _params) do
|
||||||
# The home page is often custom made,
|
render(conn, :home)
|
||||||
# so skip the default app layout.
|
|
||||||
render(conn, :home, layout: false)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,223 +1 @@
|
|||||||
<link phx-track-static rel="stylesheet" href={~p"/assets/home.css"} />
|
<h1>Home</h1>
|
||||||
<.flash_group flash={@flash} />
|
|
||||||
<div class="left-[40rem] fixed inset-y-0 right-0 z-0 hidden lg:block xl:left-[50rem]">
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 1480 957"
|
|
||||||
fill="none"
|
|
||||||
aria-hidden="true"
|
|
||||||
class="absolute inset-0 h-full w-full"
|
|
||||||
preserveAspectRatio="xMinYMid slice"
|
|
||||||
>
|
|
||||||
<path fill="#EE7868" d="M0 0h1480v957H0z" />
|
|
||||||
<path
|
|
||||||
d="M137.542 466.27c-582.851-48.41-988.806-82.127-1608.412 658.2l67.39 810 3083.15-256.51L1535.94-49.622l-98.36 8.183C1269.29 281.468 734.115 515.799 146.47 467.012l-8.928-.742Z"
|
|
||||||
fill="#FF9F92"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M371.028 528.664C-169.369 304.988-545.754 149.198-1361.45 665.565l-182.58 792.025 3014.73 694.98 389.42-1689.25-96.18-22.171C1505.28 697.438 924.153 757.586 379.305 532.09l-8.277-3.426Z"
|
|
||||||
fill="#FA8372"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M359.326 571.714C-104.765 215.795-428.003-32.102-1349.55 255.554l-282.3 1224.596 3047.04 722.01 312.24-1354.467C1411.25 1028.3 834.355 935.995 366.435 577.166l-7.109-5.452Z"
|
|
||||||
fill="#E96856"
|
|
||||||
fill-opacity=".6"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M1593.87 1236.88c-352.15 92.63-885.498-145.85-1244.602-613.557l-5.455-7.105C-12.347 152.31-260.41-170.8-1225-131.458l-368.63 1599.048 3057.19 704.76 130.31-935.47Z"
|
|
||||||
fill="#C42652"
|
|
||||||
fill-opacity=".2"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M1411.91 1526.93c-363.79 15.71-834.312-330.6-1085.883-863.909l-3.822-8.102C72.704 125.95-101.074-242.476-1052.01-408.907l-699.85 1484.267 2837.75 1338.01 326.02-886.44Z"
|
|
||||||
fill="#A41C42"
|
|
||||||
fill-opacity=".2"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M1116.26 1863.69c-355.457-78.98-720.318-535.27-825.287-1115.521l-1.594-8.816C185.286 163.833 112.786-237.016-762.678-643.898L-1822.83 608.665 571.922 2635.55l544.338-771.86Z"
|
|
||||||
fill="#A41C42"
|
|
||||||
fill-opacity=".2"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="px-4 py-10 sm:px-6 sm:py-28 lg:px-8 xl:px-28 xl:py-32">
|
|
||||||
<div class="mx-auto max-w-xl lg:mx-0">
|
|
||||||
<svg viewBox="0 0 71 48" class="h-12" aria-hidden="true">
|
|
||||||
<path
|
|
||||||
d="m26.371 33.477-.552-.1c-3.92-.729-6.397-3.1-7.57-6.829-.733-2.324.597-4.035 3.035-4.148 1.995-.092 3.362 1.055 4.57 2.39 1.557 1.72 2.984 3.558 4.514 5.305 2.202 2.515 4.797 4.134 8.347 3.634 3.183-.448 5.958-1.725 8.371-3.828.363-.316.761-.592 1.144-.886l-.241-.284c-2.027.63-4.093.841-6.205.735-3.195-.16-6.24-.828-8.964-2.582-2.486-1.601-4.319-3.746-5.19-6.611-.704-2.315.736-3.934 3.135-3.6.948.133 1.746.56 2.463 1.165.583.493 1.143 1.015 1.738 1.493 2.8 2.25 6.712 2.375 10.265-.068-5.842-.026-9.817-3.24-13.308-7.313-1.366-1.594-2.7-3.216-4.095-4.785-2.698-3.036-5.692-5.71-9.79-6.623C12.8-.623 7.745.14 2.893 2.361 1.926 2.804.997 3.319 0 4.149c.494 0 .763.006 1.032 0 2.446-.064 4.28 1.023 5.602 3.024.962 1.457 1.415 3.104 1.761 4.798.513 2.515.247 5.078.544 7.605.761 6.494 4.08 11.026 10.26 13.346 2.267.852 4.591 1.135 7.172.555ZM10.751 3.852c-.976.246-1.756-.148-2.56-.962 1.377-.343 2.592-.476 3.897-.528-.107.848-.607 1.306-1.336 1.49Zm32.002 37.924c-.085-.626-.62-.901-1.04-1.228-1.857-1.446-4.03-1.958-6.333-2-1.375-.026-2.735-.128-4.031-.61-.595-.22-1.26-.505-1.244-1.272.015-.78.693-1 1.31-1.184.505-.15 1.026-.247 1.6-.382-1.46-.936-2.886-1.065-4.787-.3-2.993 1.202-5.943 1.06-8.926-.017-1.684-.608-3.179-1.563-4.735-2.408l-.043.03a2.96 2.96 0 0 0 .04-.029c-.038-.117-.107-.12-.197-.054l.122.107c1.29 2.115 3.034 3.817 5.004 5.271 3.793 2.8 7.936 4.471 12.784 3.73A66.714 66.714 0 0 1 37 40.877c1.98-.16 3.866.398 5.753.899Zm-9.14-30.345c-.105-.076-.206-.266-.42-.069 1.745 2.36 3.985 4.098 6.683 5.193 4.354 1.767 8.773 2.07 13.293.51 3.51-1.21 6.033-.028 7.343 3.38.19-3.955-2.137-6.837-5.843-7.401-2.084-.318-4.01.373-5.962.94-5.434 1.575-10.485.798-15.094-2.553Zm27.085 15.425c.708.059 1.416.123 2.124.185-1.6-1.405-3.55-1.517-5.523-1.404-3.003.17-5.167 1.903-7.14 3.972-1.739 1.824-3.31 3.87-5.903 4.604.043.078.054.117.066.117.35.005.699.021 1.047.005 3.768-.17 7.317-.965 10.14-3.7.89-.86 1.685-1.817 2.544-2.71.716-.746 1.584-1.159 2.645-1.07Zm-8.753-4.67c-2.812.246-5.254 1.409-7.548 2.943-1.766 1.18-3.654 1.738-5.776 1.37-.374-.066-.75-.114-1.124-.17l-.013.156c.135.07.265.151.405.207.354.14.702.308 1.07.395 4.083.971 7.992.474 11.516-1.803 2.221-1.435 4.521-1.707 7.013-1.336.252.038.503.083.756.107.234.022.479.255.795.003-2.179-1.574-4.526-2.096-7.094-1.872Zm-10.049-9.544c1.475.051 2.943-.142 4.486-1.059-.452.04-.643.04-.827.076-2.126.424-4.033-.04-5.733-1.383-.623-.493-1.257-.974-1.889-1.457-2.503-1.914-5.374-2.555-8.514-2.5.05.154.054.26.108.315 3.417 3.455 7.371 5.836 12.369 6.008Zm24.727 17.731c-2.114-2.097-4.952-2.367-7.578-.537 1.738.078 3.043.632 4.101 1.728.374.388.763.768 1.182 1.106 1.6 1.29 4.311 1.352 5.896.155-1.861-.726-1.861-.726-3.601-2.452Zm-21.058 16.06c-1.858-3.46-4.981-4.24-8.59-4.008a9.667 9.667 0 0 1 2.977 1.39c.84.586 1.547 1.311 2.243 2.055 1.38 1.473 3.534 2.376 4.962 2.07-.656-.412-1.238-.848-1.592-1.507Zm17.29-19.32c0-.023.001-.045.003-.068l-.006.006.006-.006-.036-.004.021.018.012.053Zm-20 14.744a7.61 7.61 0 0 0-.072-.041.127.127 0 0 0 .015.043c.005.008.038 0 .058-.002Zm-.072-.041-.008-.034-.008.01.008-.01-.022-.006.005.026.024.014Z"
|
|
||||||
fill="#FD4F00"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<h1 class="text-brand mt-10 flex items-center text-sm font-semibold leading-6">
|
|
||||||
Phoenix Framework
|
|
||||||
<small class="bg-brand/5 text-[0.8125rem] ml-3 rounded-full px-2 font-medium leading-6">
|
|
||||||
v<%= Application.spec(:phoenix, :vsn) %>
|
|
||||||
</small>
|
|
||||||
</h1>
|
|
||||||
<p class="text-[2rem] mt-4 font-semibold leading-10 tracking-tighter text-zinc-900 text-balance">
|
|
||||||
Peace of mind from prototype to production.
|
|
||||||
</p>
|
|
||||||
<p class="mt-4 text-base leading-7 text-zinc-600">
|
|
||||||
Build rich, interactive web applications quickly, with less code and fewer moving parts. Join our growing community of developers using Phoenix to craft APIs, HTML5 apps and more, for fun or at scale.
|
|
||||||
</p>
|
|
||||||
<div class="flex">
|
|
||||||
<div class="w-full sm:w-auto">
|
|
||||||
<div class="mt-10 grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-3">
|
|
||||||
<a
|
|
||||||
href="https://hexdocs.pm/phoenix/overview.html"
|
|
||||||
class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-zinc-900 sm:py-6"
|
|
||||||
>
|
|
||||||
<span class="absolute inset-0 rounded-2xl bg-zinc-50 transition group-hover:bg-zinc-100 sm:group-hover:scale-105">
|
|
||||||
</span>
|
|
||||||
<span class="relative flex items-center gap-4 sm:flex-col">
|
|
||||||
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-6 w-6">
|
|
||||||
<path d="m12 4 10-2v18l-10 2V4Z" fill="#18181B" fill-opacity=".15" />
|
|
||||||
<path
|
|
||||||
d="M12 4 2 2v18l10 2m0-18v18m0-18 10-2v18l-10 2"
|
|
||||||
stroke="#18181B"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Guides & Docs
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://github.com/phoenixframework/phoenix"
|
|
||||||
class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-zinc-900 sm:py-6"
|
|
||||||
>
|
|
||||||
<span class="absolute inset-0 rounded-2xl bg-zinc-50 transition group-hover:bg-zinc-100 sm:group-hover:scale-105">
|
|
||||||
</span>
|
|
||||||
<span class="relative flex items-center gap-4 sm:flex-col">
|
|
||||||
<svg viewBox="0 0 24 24" aria-hidden="true" class="h-6 w-6">
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
d="M12 0C5.37 0 0 5.506 0 12.303c0 5.445 3.435 10.043 8.205 11.674.6.107.825-.262.825-.585 0-.292-.015-1.261-.015-2.291C6 21.67 5.22 20.346 4.98 19.654c-.135-.354-.72-1.446-1.23-1.738-.42-.23-1.02-.8-.015-.815.945-.015 1.62.892 1.845 1.261 1.08 1.86 2.805 1.338 3.495 1.015.105-.8.42-1.338.765-1.645-2.67-.308-5.46-1.37-5.46-6.075 0-1.338.465-2.446 1.23-3.307-.12-.308-.54-1.569.12-3.26 0 0 1.005-.323 3.3 1.26.96-.276 1.98-.415 3-.415s2.04.139 3 .416c2.295-1.6 3.3-1.261 3.3-1.261.66 1.691.24 2.952.12 3.26.765.861 1.23 1.953 1.23 3.307 0 4.721-2.805 5.767-5.475 6.075.435.384.81 1.122.81 2.276 0 1.645-.015 2.968-.015 3.383 0 .323.225.707.825.585a12.047 12.047 0 0 0 5.919-4.489A12.536 12.536 0 0 0 24 12.304C24 5.505 18.63 0 12 0Z"
|
|
||||||
fill="#18181B"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Source Code
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href={"https://github.com/phoenixframework/phoenix/blob/v#{Application.spec(:phoenix, :vsn)}/CHANGELOG.md"}
|
|
||||||
class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-zinc-900 sm:py-6"
|
|
||||||
>
|
|
||||||
<span class="absolute inset-0 rounded-2xl bg-zinc-50 transition group-hover:bg-zinc-100 sm:group-hover:scale-105">
|
|
||||||
</span>
|
|
||||||
<span class="relative flex items-center gap-4 sm:flex-col">
|
|
||||||
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-6 w-6">
|
|
||||||
<path
|
|
||||||
d="M12 1v6M12 17v6"
|
|
||||||
stroke="#18181B"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
<circle
|
|
||||||
cx="12"
|
|
||||||
cy="12"
|
|
||||||
r="4"
|
|
||||||
fill="#18181B"
|
|
||||||
fill-opacity=".15"
|
|
||||||
stroke="#18181B"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Changelog
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="mt-10 grid grid-cols-1 gap-y-4 text-sm leading-6 text-zinc-700 sm:grid-cols-2">
|
|
||||||
<div>
|
|
||||||
<a
|
|
||||||
href="https://twitter.com/elixirphoenix"
|
|
||||||
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
aria-hidden="true"
|
|
||||||
class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600"
|
|
||||||
>
|
|
||||||
<path d="M5.403 14c5.283 0 8.172-4.617 8.172-8.62 0-.131 0-.262-.008-.391A6.033 6.033 0 0 0 15 3.419a5.503 5.503 0 0 1-1.65.477 3.018 3.018 0 0 0 1.263-1.676 5.579 5.579 0 0 1-1.824.736 2.832 2.832 0 0 0-1.63-.916 2.746 2.746 0 0 0-1.821.319A2.973 2.973 0 0 0 8.076 3.78a3.185 3.185 0 0 0-.182 1.938 7.826 7.826 0 0 1-3.279-.918 8.253 8.253 0 0 1-2.64-2.247 3.176 3.176 0 0 0-.315 2.208 3.037 3.037 0 0 0 1.203 1.836A2.739 2.739 0 0 1 1.56 6.22v.038c0 .7.23 1.377.65 1.919.42.54 1.004.912 1.654 1.05-.423.122-.866.14-1.297.052.184.602.541 1.129 1.022 1.506a2.78 2.78 0 0 0 1.662.598 5.656 5.656 0 0 1-2.007 1.074A5.475 5.475 0 0 1 1 12.64a7.827 7.827 0 0 0 4.403 1.358" />
|
|
||||||
</svg>
|
|
||||||
Follow on Twitter
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a
|
|
||||||
href="https://elixirforum.com"
|
|
||||||
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
aria-hidden="true"
|
|
||||||
class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600"
|
|
||||||
>
|
|
||||||
<path d="M8 13.833c3.866 0 7-2.873 7-6.416C15 3.873 11.866 1 8 1S1 3.873 1 7.417c0 1.081.292 2.1.808 2.995.606 1.05.806 2.399.086 3.375l-.208.283c-.285.386-.01.905.465.85.852-.098 2.048-.318 3.137-.81a3.717 3.717 0 0 1 1.91-.318c.263.027.53.041.802.041Z" />
|
|
||||||
</svg>
|
|
||||||
Discuss on the Elixir Forum
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a
|
|
||||||
href="https://web.libera.chat/#elixir"
|
|
||||||
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
aria-hidden="true"
|
|
||||||
class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
d="M6.356 2.007a.75.75 0 0 1 .637.849l-1.5 10.5a.75.75 0 1 1-1.485-.212l1.5-10.5a.75.75 0 0 1 .848-.637ZM11.356 2.008a.75.75 0 0 1 .637.848l-1.5 10.5a.75.75 0 0 1-1.485-.212l1.5-10.5a.75.75 0 0 1 .848-.636Z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
d="M14 5.25a.75.75 0 0 1-.75.75h-9.5a.75.75 0 0 1 0-1.5h9.5a.75.75 0 0 1 .75.75ZM13 10.75a.75.75 0 0 1-.75.75h-9.5a.75.75 0 0 1 0-1.5h9.5a.75.75 0 0 1 .75.75Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Chat on Libera IRC
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a
|
|
||||||
href="https://discord.gg/elixir"
|
|
||||||
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
aria-hidden="true"
|
|
||||||
class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600"
|
|
||||||
>
|
|
||||||
<path d="M13.545 2.995c-1.02-.46-2.114-.8-3.257-.994a.05.05 0 0 0-.052.024c-.141.246-.297.567-.406.82a12.377 12.377 0 0 0-3.658 0 8.238 8.238 0 0 0-.412-.82.052.052 0 0 0-.052-.024 13.315 13.315 0 0 0-3.257.994.046.046 0 0 0-.021.018C.356 6.063-.213 9.036.066 11.973c.001.015.01.029.02.038a13.353 13.353 0 0 0 3.996 1.987.052.052 0 0 0 .056-.018c.308-.414.582-.85.818-1.309a.05.05 0 0 0-.028-.069 8.808 8.808 0 0 1-1.248-.585.05.05 0 0 1-.005-.084c.084-.062.168-.126.248-.191a.05.05 0 0 1 .051-.007c2.619 1.176 5.454 1.176 8.041 0a.05.05 0 0 1 .053.006c.08.065.164.13.248.192a.05.05 0 0 1-.004.084c-.399.23-.813.423-1.249.585a.05.05 0 0 0-.027.07c.24.457.514.893.817 1.307a.051.051 0 0 0 .056.019 13.31 13.31 0 0 0 4.001-1.987.05.05 0 0 0 .021-.037c.334-3.396-.559-6.345-2.365-8.96a.04.04 0 0 0-.021-.02Zm-8.198 7.19c-.789 0-1.438-.712-1.438-1.587 0-.874.637-1.586 1.438-1.586.807 0 1.45.718 1.438 1.586 0 .875-.637 1.587-1.438 1.587Zm5.316 0c-.788 0-1.438-.712-1.438-1.587 0-.874.637-1.586 1.438-1.586.807 0 1.45.718 1.438 1.586 0 .875-.63 1.587-1.438 1.587Z" />
|
|
||||||
</svg>
|
|
||||||
Join our Discord server
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a
|
|
||||||
href="https://fly.io/docs/elixir/getting-started/"
|
|
||||||
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
aria-hidden="true"
|
|
||||||
class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600"
|
|
||||||
>
|
|
||||||
<path d="M1 12.5A4.5 4.5 0 005.5 17H15a4 4 0 001.866-7.539 3.504 3.504 0 00-4.504-4.272A4.5 4.5 0 004.06 8.235 4.502 4.502 0 001 12.5z" />
|
|
||||||
</svg>
|
|
||||||
Deploy your application
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|||||||
83
lib/send_it_web/live/message_live/form_component.ex
Normal file
83
lib/send_it_web/live/message_live/form_component.ex
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
defmodule SendItWeb.MessageLive.FormComponent do
|
||||||
|
use SendItWeb, :live_component
|
||||||
|
|
||||||
|
alias SendIt.Marketing
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def render(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div>
|
||||||
|
<.header>
|
||||||
|
<%= @title %>
|
||||||
|
<:subtitle>Use this form to manage message records in your database.</:subtitle>
|
||||||
|
</.header>
|
||||||
|
|
||||||
|
<.simple_form
|
||||||
|
for={@form}
|
||||||
|
id="message-form"
|
||||||
|
phx-target={@myself}
|
||||||
|
phx-change="validate"
|
||||||
|
phx-submit="save"
|
||||||
|
>
|
||||||
|
<.input field={@form[:subject]} type="text" label="Subject" />
|
||||||
|
<.input field={@form[:content]} type="text" label="Content" />
|
||||||
|
<:actions>
|
||||||
|
<.button phx-disable-with="Saving...">Save Message</.button>
|
||||||
|
</:actions>
|
||||||
|
</.simple_form>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def update(%{message: message} = assigns, socket) do
|
||||||
|
{:ok,
|
||||||
|
socket
|
||||||
|
|> assign(assigns)
|
||||||
|
|> assign_new(:form, fn ->
|
||||||
|
to_form(Marketing.change_message(message))
|
||||||
|
end)}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("validate", %{"message" => message_params}, socket) do
|
||||||
|
changeset = Marketing.change_message(socket.assigns.message, message_params)
|
||||||
|
{:noreply, assign(socket, form: to_form(changeset, action: :validate))}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("save", %{"message" => message_params}, socket) do
|
||||||
|
save_message(socket, socket.assigns.action, message_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp save_message(socket, :edit, message_params) do
|
||||||
|
case Marketing.update_message(socket.assigns.message, message_params) do
|
||||||
|
{:ok, message} ->
|
||||||
|
notify_parent({:saved, message})
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> put_flash(:info, "Message updated successfully")
|
||||||
|
|> push_patch(to: socket.assigns.patch)}
|
||||||
|
|
||||||
|
{:error, %Ecto.Changeset{} = changeset} ->
|
||||||
|
{:noreply, assign(socket, form: to_form(changeset))}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp save_message(socket, :new, message_params) do
|
||||||
|
case Marketing.create_message(message_params) do
|
||||||
|
{:ok, message} ->
|
||||||
|
notify_parent({:saved, message})
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> put_flash(:info, "Message created successfully")
|
||||||
|
|> push_patch(to: socket.assigns.patch)}
|
||||||
|
|
||||||
|
{:error, %Ecto.Changeset{} = changeset} ->
|
||||||
|
{:noreply, assign(socket, form: to_form(changeset))}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
|
||||||
|
end
|
||||||
47
lib/send_it_web/live/message_live/index.ex
Normal file
47
lib/send_it_web/live/message_live/index.ex
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
defmodule SendItWeb.MessageLive.Index do
|
||||||
|
use SendItWeb, :live_view
|
||||||
|
|
||||||
|
alias SendIt.Marketing
|
||||||
|
alias SendIt.Marketing.Message
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def mount(_params, _session, socket) do
|
||||||
|
{:ok, stream(socket, :messages, Marketing.list_messages())}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_params(params, _url, socket) do
|
||||||
|
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp apply_action(socket, :edit, %{"id" => id}) do
|
||||||
|
socket
|
||||||
|
|> assign(:page_title, "Edit Message")
|
||||||
|
|> assign(:message, Marketing.get_message!(id))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp apply_action(socket, :new, _params) do
|
||||||
|
socket
|
||||||
|
|> assign(:page_title, "New Message")
|
||||||
|
|> assign(:message, %Message{})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp apply_action(socket, :index, _params) do
|
||||||
|
socket
|
||||||
|
|> assign(:page_title, "Listing Messages")
|
||||||
|
|> assign(:message, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_info({SendItWeb.MessageLive.FormComponent, {:saved, message}}, socket) do
|
||||||
|
{:noreply, stream_insert(socket, :messages, message)}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("delete", %{"id" => id}, socket) do
|
||||||
|
message = Marketing.get_message!(id)
|
||||||
|
{:ok, _} = Marketing.delete_message(message)
|
||||||
|
|
||||||
|
{:noreply, stream_delete(socket, :messages, message)}
|
||||||
|
end
|
||||||
|
end
|
||||||
51
lib/send_it_web/live/message_live/index.html.heex
Normal file
51
lib/send_it_web/live/message_live/index.html.heex
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<.header>
|
||||||
|
Listing Messages
|
||||||
|
<:actions>
|
||||||
|
<.link patch={~p"/messages/new"}>
|
||||||
|
<.button>New Message</.button>
|
||||||
|
</.link>
|
||||||
|
</:actions>
|
||||||
|
</.header>
|
||||||
|
|
||||||
|
<.table
|
||||||
|
id="messages"
|
||||||
|
rows={@streams.messages}
|
||||||
|
row_click={fn {_id, message} -> JS.navigate(~p"/messages/#{message}") end}
|
||||||
|
>
|
||||||
|
<:col :let={{_id, message}} label="Subject">
|
||||||
|
<%= message.subject %>
|
||||||
|
</:col>
|
||||||
|
<:col :let={{_id, message}} label="Content">
|
||||||
|
<%= message.content %>
|
||||||
|
</:col>
|
||||||
|
<:action :let={{_id, message}}>
|
||||||
|
<div class="sr-only">
|
||||||
|
<.link navigate={~p"/messages/#{message}"}>Show</.link>
|
||||||
|
</div>
|
||||||
|
<.link patch={~p"/messages/#{message}/edit"}>Edit</.link>
|
||||||
|
</:action>
|
||||||
|
<:action :let={{id, message}}>
|
||||||
|
<.link
|
||||||
|
phx-click={JS.push("delete", value: %{id: message.id}) |> hide("##{id}")}
|
||||||
|
data-confirm="Are you sure?"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</.link>
|
||||||
|
</:action>
|
||||||
|
</.table>
|
||||||
|
|
||||||
|
<.modal
|
||||||
|
:if={@live_action in [:new, :edit]}
|
||||||
|
id="message-modal"
|
||||||
|
show
|
||||||
|
on_cancel={JS.patch(~p"/messages")}
|
||||||
|
>
|
||||||
|
<.live_component
|
||||||
|
module={SendItWeb.MessageLive.FormComponent}
|
||||||
|
id={@message.id || :new}
|
||||||
|
title={@page_title}
|
||||||
|
action={@live_action}
|
||||||
|
message={@message}
|
||||||
|
patch={~p"/messages"}
|
||||||
|
/>
|
||||||
|
</.modal>
|
||||||
21
lib/send_it_web/live/message_live/show.ex
Normal file
21
lib/send_it_web/live/message_live/show.ex
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
defmodule SendItWeb.MessageLive.Show do
|
||||||
|
use SendItWeb, :live_view
|
||||||
|
|
||||||
|
alias SendIt.Marketing
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def mount(_params, _session, socket) do
|
||||||
|
{:ok, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_params(%{"id" => id}, _, socket) do
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:page_title, page_title(socket.assigns.live_action))
|
||||||
|
|> assign(:message, Marketing.get_message!(id))}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp page_title(:show), do: "Show Message"
|
||||||
|
defp page_title(:edit), do: "Edit Message"
|
||||||
|
end
|
||||||
36
lib/send_it_web/live/message_live/show.html.heex
Normal file
36
lib/send_it_web/live/message_live/show.html.heex
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<.header>
|
||||||
|
Message <%= @message.id %>
|
||||||
|
<:subtitle>This is a message record from your database.</:subtitle>
|
||||||
|
<:actions>
|
||||||
|
<.link patch={~p"/messages/#{@message}/show/edit"} phx-click={JS.push_focus()}>
|
||||||
|
<.button>Edit message</.button>
|
||||||
|
</.link>
|
||||||
|
</:actions>
|
||||||
|
</.header>
|
||||||
|
|
||||||
|
<.list>
|
||||||
|
<:item title="Subject">
|
||||||
|
<%= @message.subject %>
|
||||||
|
</:item>
|
||||||
|
<:item title="Content">
|
||||||
|
<%= @message.content %>
|
||||||
|
</:item>
|
||||||
|
</.list>
|
||||||
|
|
||||||
|
<.back navigate={~p"/messages"}>Back to messages</.back>
|
||||||
|
|
||||||
|
<.modal
|
||||||
|
:if={@live_action == :edit}
|
||||||
|
id="message-modal"
|
||||||
|
show
|
||||||
|
on_cancel={JS.patch(~p"/messages/#{@message}")}
|
||||||
|
>
|
||||||
|
<.live_component
|
||||||
|
module={SendItWeb.MessageLive.FormComponent}
|
||||||
|
id={@message.id}
|
||||||
|
title={@page_title}
|
||||||
|
action={@live_action}
|
||||||
|
message={@message}
|
||||||
|
patch={~p"/messages/#{@message}"}
|
||||||
|
/>
|
||||||
|
</.modal>
|
||||||
@ -66,6 +66,13 @@ defmodule SendItWeb.Router do
|
|||||||
|
|
||||||
live_session :require_authenticated_user,
|
live_session :require_authenticated_user,
|
||||||
on_mount: [{SendItWeb.UserAuth, :ensure_authenticated}] do
|
on_mount: [{SendItWeb.UserAuth, :ensure_authenticated}] do
|
||||||
|
live "/messages", MessageLive.Index, :index
|
||||||
|
live "/messages/new", MessageLive.Index, :new
|
||||||
|
live "/messages/:id/edit", MessageLive.Index, :edit
|
||||||
|
|
||||||
|
live "/messages/:id", MessageLive.Show, :show
|
||||||
|
live "/messages/:id/show/edit", MessageLive.Show, :edit
|
||||||
|
|
||||||
live "/users/settings", UserSettingsLive, :edit
|
live "/users/settings", UserSettingsLive, :edit
|
||||||
live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email
|
live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email
|
||||||
end
|
end
|
||||||
|
|||||||
2
mix.exs
2
mix.exs
@ -40,7 +40,7 @@ defmodule SendIt.MixProject do
|
|||||||
{:phoenix_html, "~> 4.1"},
|
{:phoenix_html, "~> 4.1"},
|
||||||
{:phoenix_live_reload, "~> 1.2", only: :dev},
|
{:phoenix_live_reload, "~> 1.2", only: :dev},
|
||||||
# TODO bump on release to {:phoenix_live_view, "~> 1.0.0"},
|
# TODO bump on release to {:phoenix_live_view, "~> 1.0.0"},
|
||||||
{:phoenix_live_view, "~> 1.0.0-rc.1", override: true},
|
{:phoenix_live_view, "~> 1.0.0-rc.7", override: true},
|
||||||
{:floki, ">= 0.30.0", only: :test},
|
{:floki, ">= 0.30.0", only: :test},
|
||||||
{:phoenix_live_dashboard, "~> 0.8.3"},
|
{:phoenix_live_dashboard, "~> 0.8.3"},
|
||||||
{:esbuild, "~> 0.8", runtime: Mix.env() == :dev},
|
{:esbuild, "~> 0.8", runtime: Mix.env() == :dev},
|
||||||
|
|||||||
3
mix.lock
3
mix.lock
@ -1,11 +1,14 @@
|
|||||||
%{
|
%{
|
||||||
"bandit": {:hex, :bandit, "1.5.7", "6856b1e1df4f2b0cb3df1377eab7891bec2da6a7fd69dc78594ad3e152363a50", [:mix], [{:hpax, "~> 1.0.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "f2dd92ae87d2cbea2fa9aa1652db157b6cba6c405cb44d4f6dd87abba41371cd"},
|
"bandit": {:hex, :bandit, "1.5.7", "6856b1e1df4f2b0cb3df1377eab7891bec2da6a7fd69dc78594ad3e152363a50", [:mix], [{:hpax, "~> 1.0.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "f2dd92ae87d2cbea2fa9aa1652db157b6cba6c405cb44d4f6dd87abba41371cd"},
|
||||||
|
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.2.0", "feab711974beba4cb348147170346fe097eea2e840db4e012a145e180ed4ab75", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "563e92a6c77d667b19c5f4ba17ab6d440a085696bdf4c68b9b0f5b30bc5422b8"},
|
||||||
"castore": {:hex, :castore, "1.0.10", "43bbeeac820f16c89f79721af1b3e092399b3a1ecc8df1a472738fd853574911", [:mix], [], "hexpm", "1b0b7ea14d889d9ea21202c43a4fa015eb913021cb535e8ed91946f4b77a8848"},
|
"castore": {:hex, :castore, "1.0.10", "43bbeeac820f16c89f79721af1b3e092399b3a1ecc8df1a472738fd853574911", [:mix], [], "hexpm", "1b0b7ea14d889d9ea21202c43a4fa015eb913021cb535e8ed91946f4b77a8848"},
|
||||||
|
"comeonin": {:hex, :comeonin, "5.5.0", "364d00df52545c44a139bad919d7eacb55abf39e86565878e17cebb787977368", [:mix], [], "hexpm", "6287fc3ba0aad34883cbe3f7949fc1d1e738e5ccdce77165bc99490aa69f47fb"},
|
||||||
"db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"},
|
"db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"},
|
||||||
"decimal": {:hex, :decimal, "2.2.0", "df3d06bb9517e302b1bd265c1e7f16cda51547ad9d99892049340841f3e15836", [:mix], [], "hexpm", "af8daf87384b51b7e611fb1a1f2c4d4876b65ef968fa8bd3adf44cff401c7f21"},
|
"decimal": {:hex, :decimal, "2.2.0", "df3d06bb9517e302b1bd265c1e7f16cda51547ad9d99892049340841f3e15836", [:mix], [], "hexpm", "af8daf87384b51b7e611fb1a1f2c4d4876b65ef968fa8bd3adf44cff401c7f21"},
|
||||||
"dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"},
|
"dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"},
|
||||||
"ecto": {:hex, :ecto, "3.12.4", "267c94d9f2969e6acc4dd5e3e3af5b05cdae89a4d549925f3008b2b7eb0b93c3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ef04e4101688a67d061e1b10d7bc1fbf00d1d13c17eef08b71d070ff9188f747"},
|
"ecto": {:hex, :ecto, "3.12.4", "267c94d9f2969e6acc4dd5e3e3af5b05cdae89a4d549925f3008b2b7eb0b93c3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ef04e4101688a67d061e1b10d7bc1fbf00d1d13c17eef08b71d070ff9188f747"},
|
||||||
"ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"},
|
"ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"},
|
||||||
|
"elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
|
||||||
"esbuild": {:hex, :esbuild, "0.8.2", "5f379dfa383ef482b738e7771daf238b2d1cfb0222bef9d3b20d4c8f06c7a7ac", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "558a8a08ed78eb820efbfda1de196569d8bfa9b51e8371a1934fbb31345feda7"},
|
"esbuild": {:hex, :esbuild, "0.8.2", "5f379dfa383ef482b738e7771daf238b2d1cfb0222bef9d3b20d4c8f06c7a7ac", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "558a8a08ed78eb820efbfda1de196569d8bfa9b51e8371a1934fbb31345feda7"},
|
||||||
"file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"},
|
"file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"},
|
||||||
"finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"},
|
"finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"},
|
||||||
|
|||||||
15
priv/repo/migrations/20241114191936_create_contacts.exs
Normal file
15
priv/repo/migrations/20241114191936_create_contacts.exs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
defmodule SendIt.Repo.Migrations.CreateContacts do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:contacts) do
|
||||||
|
add :name, :string
|
||||||
|
add :email, :string
|
||||||
|
add :subscribed, :boolean, default: true, null: false
|
||||||
|
|
||||||
|
timestamps(type: :utc_datetime)
|
||||||
|
end
|
||||||
|
|
||||||
|
create unique_index(:contacts, [:email])
|
||||||
|
end
|
||||||
|
end
|
||||||
12
priv/repo/migrations/20241114192041_create_messages.exs
Normal file
12
priv/repo/migrations/20241114192041_create_messages.exs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
defmodule SendIt.Repo.Migrations.CreateMessages do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:messages) do
|
||||||
|
add :subject, :string
|
||||||
|
add :content, :text
|
||||||
|
|
||||||
|
timestamps(type: :utc_datetime)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
defmodule SendIt.Repo.Migrations.CreateContactsMessages do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:contacts_messages, primary_key: false) do
|
||||||
|
add :contact_id, references(:contacts, on_delete: :delete_all)
|
||||||
|
add :message_id, references(:messages, on_delete: :delete_all)
|
||||||
|
end
|
||||||
|
|
||||||
|
create unique_index(:contacts_messages, [:contact_id, :message_id])
|
||||||
|
end
|
||||||
|
end
|
||||||
119
test/send_it/marketing_test.exs
Normal file
119
test/send_it/marketing_test.exs
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
defmodule SendIt.MarketingTest do
|
||||||
|
use SendIt.DataCase
|
||||||
|
|
||||||
|
alias SendIt.Marketing
|
||||||
|
|
||||||
|
describe "contacts" do
|
||||||
|
alias SendIt.Marketing.Contact
|
||||||
|
|
||||||
|
import SendIt.MarketingFixtures
|
||||||
|
|
||||||
|
@invalid_attrs %{name: nil, email: nil, subscribed: nil}
|
||||||
|
|
||||||
|
test "list_contacts/0 returns all contacts" do
|
||||||
|
contact = contact_fixture()
|
||||||
|
assert Marketing.list_contacts() == [contact]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get_contact!/1 returns the contact with given id" do
|
||||||
|
contact = contact_fixture()
|
||||||
|
assert Marketing.get_contact!(contact.id) == contact
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create_contact/1 with valid data creates a contact" do
|
||||||
|
valid_attrs = %{name: "some name", email: "some email", subscribed: true}
|
||||||
|
|
||||||
|
assert {:ok, %Contact{} = contact} = Marketing.create_contact(valid_attrs)
|
||||||
|
assert contact.name == "some name"
|
||||||
|
assert contact.email == "some email"
|
||||||
|
assert contact.subscribed == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create_contact/1 with invalid data returns error changeset" do
|
||||||
|
assert {:error, %Ecto.Changeset{}} = Marketing.create_contact(@invalid_attrs)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update_contact/2 with valid data updates the contact" do
|
||||||
|
contact = contact_fixture()
|
||||||
|
update_attrs = %{name: "some updated name", email: "some updated email", subscribed: false}
|
||||||
|
|
||||||
|
assert {:ok, %Contact{} = contact} = Marketing.update_contact(contact, update_attrs)
|
||||||
|
assert contact.name == "some updated name"
|
||||||
|
assert contact.email == "some updated email"
|
||||||
|
assert contact.subscribed == false
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update_contact/2 with invalid data returns error changeset" do
|
||||||
|
contact = contact_fixture()
|
||||||
|
assert {:error, %Ecto.Changeset{}} = Marketing.update_contact(contact, @invalid_attrs)
|
||||||
|
assert contact == Marketing.get_contact!(contact.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "delete_contact/1 deletes the contact" do
|
||||||
|
contact = contact_fixture()
|
||||||
|
assert {:ok, %Contact{}} = Marketing.delete_contact(contact)
|
||||||
|
assert_raise Ecto.NoResultsError, fn -> Marketing.get_contact!(contact.id) end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "change_contact/1 returns a contact changeset" do
|
||||||
|
contact = contact_fixture()
|
||||||
|
assert %Ecto.Changeset{} = Marketing.change_contact(contact)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "messages" do
|
||||||
|
alias SendIt.Marketing.Message
|
||||||
|
|
||||||
|
import SendIt.MarketingFixtures
|
||||||
|
|
||||||
|
@invalid_attrs %{subject: nil, content: nil}
|
||||||
|
|
||||||
|
test "list_messages/0 returns all messages" do
|
||||||
|
message = message_fixture()
|
||||||
|
assert Marketing.list_messages() == [message]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get_message!/1 returns the message with given id" do
|
||||||
|
message = message_fixture()
|
||||||
|
assert Marketing.get_message!(message.id) == message
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create_message/1 with valid data creates a message" do
|
||||||
|
valid_attrs = %{subject: "some subject", content: "some content"}
|
||||||
|
|
||||||
|
assert {:ok, %Message{} = message} = Marketing.create_message(valid_attrs)
|
||||||
|
assert message.subject == "some subject"
|
||||||
|
assert message.content == "some content"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create_message/1 with invalid data returns error changeset" do
|
||||||
|
assert {:error, %Ecto.Changeset{}} = Marketing.create_message(@invalid_attrs)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update_message/2 with valid data updates the message" do
|
||||||
|
message = message_fixture()
|
||||||
|
update_attrs = %{subject: "some updated subject", content: "some updated content"}
|
||||||
|
|
||||||
|
assert {:ok, %Message{} = message} = Marketing.update_message(message, update_attrs)
|
||||||
|
assert message.subject == "some updated subject"
|
||||||
|
assert message.content == "some updated content"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update_message/2 with invalid data returns error changeset" do
|
||||||
|
message = message_fixture()
|
||||||
|
assert {:error, %Ecto.Changeset{}} = Marketing.update_message(message, @invalid_attrs)
|
||||||
|
assert message == Marketing.get_message!(message.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "delete_message/1 deletes the message" do
|
||||||
|
message = message_fixture()
|
||||||
|
assert {:ok, %Message{}} = Marketing.delete_message(message)
|
||||||
|
assert_raise Ecto.NoResultsError, fn -> Marketing.get_message!(message.id) end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "change_message/1 returns a message changeset" do
|
||||||
|
message = message_fixture()
|
||||||
|
assert %Ecto.Changeset{} = Marketing.change_message(message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
42
test/support/fixtures/marketing_fixtures.ex
Normal file
42
test/support/fixtures/marketing_fixtures.ex
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
defmodule SendIt.MarketingFixtures do
|
||||||
|
@moduledoc """
|
||||||
|
This module defines test helpers for creating
|
||||||
|
entities via the `SendIt.Marketing` context.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Generate a unique contact email.
|
||||||
|
"""
|
||||||
|
def unique_contact_email, do: "some email#{System.unique_integer([:positive])}"
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Generate a contact.
|
||||||
|
"""
|
||||||
|
def contact_fixture(attrs \\ %{}) do
|
||||||
|
{:ok, contact} =
|
||||||
|
attrs
|
||||||
|
|> Enum.into(%{
|
||||||
|
email: unique_contact_email(),
|
||||||
|
name: "some name",
|
||||||
|
subscribed: true
|
||||||
|
})
|
||||||
|
|> SendIt.Marketing.create_contact()
|
||||||
|
|
||||||
|
contact
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Generate a message.
|
||||||
|
"""
|
||||||
|
def message_fixture(attrs \\ %{}) do
|
||||||
|
{:ok, message} =
|
||||||
|
attrs
|
||||||
|
|> Enum.into(%{
|
||||||
|
content: "some content",
|
||||||
|
subject: "some subject"
|
||||||
|
})
|
||||||
|
|> SendIt.Marketing.create_message()
|
||||||
|
|
||||||
|
message
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
x
Reference in New Issue
Block a user