Skip to content

Commit

Permalink
Add membership card (#283)
Browse files Browse the repository at this point in the history
* Basic card

* Responsiveness

* Ability to set parameters on card

* Fix warning

* Implement suggestions

* Change name

* Update seeds
  • Loading branch information
ruioliveira02 authored May 18, 2023
1 parent a1585a9 commit 44ade10
Show file tree
Hide file tree
Showing 13 changed files with 282 additions and 3 deletions.
12 changes: 9 additions & 3 deletions lib/atomic/organizations.ex
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,25 @@ defmodule Atomic.Organizations do
## Examples
iex> update_organization(organization, %{field: new_value})
iex> update_organization(organization, %{field: new_value}, nil)
{:ok, %Organization{}}
iex> update_organization(organization, %{field: bad_value})
iex> update_organization(organization, %{field: bad_value}, nil)
{:error, %Ecto.Changeset{}}
"""
def update_organization(%Organization{} = organization, attrs) do
def update_organization(%Organization{} = organization, attrs, _after_save \\ &{:ok, &1}) do
organization
|> Organization.changeset(attrs)
|> Repo.update()
end

def update_card_image(%Organization{} = organization, attrs) do
organization
|> Organization.card_changeset(attrs)
|> Repo.update()
end

@doc """
Deletes a organization.
Expand Down
37 changes: 37 additions & 0 deletions lib/atomic/organizations/card.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
defmodule Atomic.Organizations.Card do
@moduledoc """
The membership card for an organization
"""
use Atomic.Schema
import Ecto.Changeset

@optional_fields [
:name_size,
:name_color,
:name_x,
:name_y,
:number_size,
:number_color,
:number_x,
:number_y
]

@primary_key false
embedded_schema do
field :name_size, :float
field :name_color, :string
field :name_x, :float
field :name_y, :float
field :number_size, :float
field :number_color, :string
field :number_x, :float
field :number_y, :float
end

# TODO: Validations

def changeset(card, attrs) do
card
|> cast(attrs, @optional_fields)
end
end
17 changes: 17 additions & 0 deletions lib/atomic/organizations/organization.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ defmodule Atomic.Organizations.Organization do
alias Atomic.Activities.Location
alias Atomic.Organizations.Membership
alias Atomic.Partnerships.Partner
alias Atomic.Organizations.Card
alias Atomic.Uploaders

@required_fields ~w(name description)a
@optional_fields []

schema "organizations" do
field :name, :string
field :description, :string
field :card_image, Uploaders.Card.Type

has_many :departments, Department,
on_replace: :delete_if_exists,
Expand All @@ -28,6 +31,7 @@ defmodule Atomic.Organizations.Organization do
preload_order: [asc: :name]

embeds_one :location, Location, on_replace: :delete
embeds_one :card, Card, on_replace: :delete

timestamps()
end
Expand All @@ -37,11 +41,24 @@ defmodule Atomic.Organizations.Organization do
organization
|> cast(attrs, @required_fields ++ @optional_fields)
|> validate_required(@required_fields)
|> cast_attachments(attrs, [:card_image])
|> validate_location()
|> validate_card()
end

defp validate_location(changeset) do
changeset
|> cast_embed(:location, with: &Location.changeset/2)
end

defp validate_card(changeset) do
changeset
|> cast_embed(:card, with: &Card.changeset/2)
end

def card_changeset(organization, attrs) do
organization
|> cast_attachments(attrs, [:card_image])
|> validate_card()
end
end
27 changes: 27 additions & 0 deletions lib/atomic/uploaders/card.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
defmodule Atomic.Uploaders.Card do
use Waffle.Definition
use Waffle.Ecto.Definition
alias Atomic.Organizations.Organization

@versions [:original]
@extension_whitelist ~w(.svg .jpg .jpeg .gif .png)

def validate({file, _}) do
file.file_name
|> Path.extname()
|> String.downcase()
|> then(&Enum.member?(@extension_whitelist, &1))
|> case do
true -> :ok
false -> {:error, "invalid file type"}
end
end

def filename(version, _) do
version
end

def storage_dir(_version, {_file, %Organization{} = scope}) do
"uploads/atomic/cards/#{scope.id}"
end
end
6 changes: 6 additions & 0 deletions lib/atomic_web/endpoint.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ defmodule AtomicWeb.Endpoint do
gzip: false,
only: ~w(assets fonts images favicon.ico robots.txt)

plug(Plug.Static,
at: "/uploads",
from: Path.expand("./priv/uploads"),
gzip: false
)

# Code reloading can be explicitly enabled under the
# :code_reloader configuration of your endpoint.
if code_reloading? do
Expand Down
23 changes: 23 additions & 0 deletions lib/atomic_web/live/card_live/show.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
defmodule AtomicWeb.CardLive.Show do
use AtomicWeb, :live_view

alias Atomic.Organizations
alias Atomic.Uploaders

@impl true
def mount(_params, _session, socket) do
{:ok, socket}
end

@impl true
def handle_params(%{"membership_id" => id}, _, socket) do
membership = Organizations.get_membership!(id, [:user, :organization])

{:noreply,
socket
|> assign(:page_title, page_title(socket.assigns.live_action))
|> assign(:membership, membership)}
end

defp page_title(:show), do: "Membership Card"
end
24 changes: 24 additions & 0 deletions lib/atomic_web/live/card_live/show.html.heex
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<%= if @membership.organization.card_image != nil do %>
<div class="fixed z-0 top-0 left-0 bg-black w-screen h-screen"></div>
<div
class=" overflow-x-hidden overflow-y-hidden sm:rotate-0 z-50 rotate-90 scale-150 sm:scale-100 fixed top-0 left-0 w-screen h-screen bg-contain bg-center bg-no-repeat"
style={"background-image: url(" <> Uploaders.Card.url({@membership.organization.card_image, @membership.organization}, :original) <> ")"}
>
<p
class="absolute top-1/2 left-1/2 "
style={"font-size: " <> to_string(@membership.organization.card.name_size) <> "vw; color: " <> @membership.organization.card.name_color <> "; transform: translate(" <> to_string(@membership.organization.card.name_x) <> "%, " <> to_string(@membership.organization.card.name_y) <> "%);"}
>
<%= @membership.user.name %>
</p>
<p
class="absolute top-1/2 left-1/2 "
style={"font-size: " <> to_string(@membership.organization.card.number_size) <> "vw; color: " <> @membership.organization.card.number_color <> "; transform: translate(" <> to_string(@membership.organization.card.number_x) <> "%, " <> to_string(@membership.organization.card.number_y) <> "%);"}
>
<%= @membership.number %>
</p>
</div>
<% else %>
<div>
<h1>This organization does not have a membership card</h1>
</div>
<% end %>
24 changes: 24 additions & 0 deletions lib/atomic_web/live/organization_live/form_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ defmodule AtomicWeb.OrganizationLive.FormComponent do
alias Atomic.Departments
alias Atomic.Activities

@extensions_whitelist ~w(.svg .jpg .jpeg .gif .png)

@impl true
def mount(socket) do
departments = Departments.list_departments()
speakers = Activities.list_speakers()

{:ok,
socket
|> allow_upload(:card, accept: @extensions_whitelist, max_entries: 1)
|> assign(:speakers, speakers)
|> assign(:departments, departments)}
end
Expand Down Expand Up @@ -41,6 +44,8 @@ defmodule AtomicWeb.OrganizationLive.FormComponent do
end

defp save_organization(socket, :edit, organization_params) do
consume_card_data(socket, socket.assigns.organization)

case Organizations.update_organization(socket.assigns.organization, organization_params) do
{:ok, _organization} ->
{:noreply,
Expand All @@ -65,4 +70,23 @@ defmodule AtomicWeb.OrganizationLive.FormComponent do
{:noreply, assign(socket, changeset: changeset)}
end
end

defp consume_card_data(socket, organization) do
consume_uploaded_entries(socket, :card, fn %{path: path}, entry ->
Organizations.update_card_image(organization, %{
"card_image" => %Plug.Upload{
content_type: entry.client_type,
filename: entry.client_name,
path: path
}
})
end)
|> case do
[{:ok, organization}] ->
{:ok, organization}

_errors ->
{:ok, organization}
end
end
end
73 changes: 73 additions & 0 deletions lib/atomic_web/live/organization_live/form_component.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,79 @@

<%= label(f, :departments) %>
<%= multiple_select(f, :departments, Enum.map(@departments, &{&1.name, &1.id}), selected: Enum.map(@departments, & &1.id)) %>

<div class="py-5 px-4 sm:px-6 shrink-0 1.5xl:py-5 1.5xl:px-6 1.5xl:shrink-0">
<%= live_file_input(@uploads.card, class: "hidden") %>
<div
class={
"#{if length(@uploads.card.entries) != 0 do
"hidden"
end} border-2 border-gray-300 border-dashed rounded-md"
}
phx-drop-target={@uploads.card.ref}
>
<div class="">
<div class="">
<div class="flex flex-col h-screen space-y-1 text-center">
<div class="flex text-sm text-gray-600">
<label for="file-upload" class="relative font-medium text-red-600 rounded-md cursor-pointer hover:text-red-800">
<a onclick={"document.getElementById('#{@uploads.card.ref}').click()"}>
Upload a file
</a>
</label>
<p class="pl-1">or drag and drop</p>
</div>
<svg class="mx-auto w-12 h-1 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48" aria-hidden="true">
<path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<p class="text-xs text-gray-500">
PNG, JPG, GIF up to 10MB
</p>
</div>
</div>
</div>
</div>
<section>
<%= for entry <- @uploads.card.entries do %>
<%= for err <- upload_errors(@uploads.card, entry) do %>
<p class="alert alert-danger"><%= Phoenix.Naming.humanize(err) %></p>
<% end %>
<article class="upload-entry">
<figure class="w-[400px]">
<%= live_img_preview(entry) %>
<div class="flex">
<figcaption>
<%= if String.length(entry.client_name) < 30 do
entry.client_name
else
String.slice(entry.client_name, 0..30) <> "... "
end %>
</figcaption>
<button type="button" phx-click="cancel-card" phx-target={@myself} phx-value-ref={entry.ref} aria-label="cancel" class="pl-4"></button>
</div>
</figure>
</article>
<% end %>
</section>
</div>
<%= inputs_for f, :card, fn ff -> %>
<%= label(ff, :number_x) %>
<%= number_input(ff, :number_x) %>
<%= label(ff, :number_y) %>
<%= number_input(ff, :number_y) %>
<%= label(ff, :number_size) %>
<%= number_input(ff, :number_size) %>
<%= label(ff, :number_color) %>
<%= text_input(ff, :number_color) %>
<%= label(ff, :name_x) %>
<%= number_input(ff, :name_x) %>
<%= label(ff, :name_y) %>
<%= number_input(ff, :name_y) %>
<%= label(ff, :name_size) %>
<%= number_input(ff, :name_size) %>
<%= label(ff, :name_color) %>
<%= text_input(ff, :name_color) %>
<% end %>
<%= error_tag(f, :departments) %>

<div>
Expand Down
2 changes: 2 additions & 0 deletions lib/atomic_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ defmodule AtomicWeb.Router do
live "/membership/:org/:id", MembershipLive.Show, :show
live "/membership/:org/:id/edit", MembershipLive.Edit, :edit

live "/card/:membership_id", CardLive.Show, :show

live "/board/:org", BoardLive.Index, :index
live "/board/:org/new", BoardLive.New, :new
live "/board/:org/:id", BoardLive.Show, :show
Expand Down
10 changes: 10 additions & 0 deletions priv/repo/migrations/20230430092354_add_cards.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
defmodule Atomic.Repo.Migrations.AddCards do
use Ecto.Migration

def change do
alter table(:organizations) do
add :card_image, :string
add :card, :map, default: %{}, null: true
end
end
end
Loading

0 comments on commit 44ade10

Please sign in to comment.