Skip to content

Commit

Permalink
added all form fields, successfully submitting and uploading
Browse files Browse the repository at this point in the history
  • Loading branch information
zackcreach committed Dec 26, 2024
1 parent 89083bf commit 06fc41a
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 43 deletions.
2 changes: 1 addition & 1 deletion assets/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module.exports = {
content: [
'./js/**/*.js',
'../lib/gifmaster_web.ex',
'../lib/gifmaster_web/**/*.*ex',
'../lib/gifmaster_web/**/*',
],
theme: {
extend: {
Expand Down
91 changes: 56 additions & 35 deletions lib/gifmaster_web/components/core_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@ defmodule GifmasterWeb.CoreComponents do
<div id={"#{@id}-bg"} class="bg-black/50 fixed inset-0 transition-opacity" aria-hidden="true" />
<div class="fixed inset-0 overflow-y-auto" aria-labelledby={"#{@id}-title"} aria-describedby={"#{@id}-description"} role="dialog" aria-modal="true" tabindex="0">
<div class="flex min-h-full items-center justify-center">
<div class="w-96 p-4 sm:p-6 lg:py-8">
<div class="w-[500px] p-4 sm:p-6 lg:py-8">
<.focus_wrap id={"#{@id}-container"} phx-window-keydown={JS.exec("data-cancel", to: "##{@id}")} phx-key="escape" phx-click-away={JS.exec("data-cancel", to: "##{@id}")} class="text-white shadow-black/10 relative hidden bg-grey-400 p-12 shadow-lg transition">
<div class="absolute top-6 right-5">
<button phx-click={JS.exec("data-cancel", to: "##{@id}")} type="button" class="-m-3 flex-none p-3 opacity-50 hover:opacity-100 transition-opacity" aria-label={gettext("close")}>
<.icon name="hero-x-mark-solid" class="size-5" />
</button>
</div>
<div id={"#{@id}-content"}>
<%= render_slot(@inner_block) %>
{render_slot(@inner_block)}
</div>
</.focus_wrap>
</div>
Expand Down Expand Up @@ -101,9 +101,9 @@ defmodule GifmasterWeb.CoreComponents do
<p :if={@title} class="flex items-center gap-1.5 text-sm font-semibold leading-6">
<.icon :if={@kind == :info} name="hero-information-circle-mini" class="h-4 w-4" />
<.icon :if={@kind == :error} name="hero-exclamation-circle-mini" class="h-4 w-4" />
<%= @title %>
{@title}
</p>
<p class="mt-2 text-sm leading-5"><%= msg %></p>
<p class="mt-2 text-sm leading-5">{msg}</p>
<button type="button" class="group absolute top-1 right-1 p-2" aria-label={gettext("close")}>
<.icon name="hero-x-mark-solid" class="h-5 w-5 opacity-40 group-hover:opacity-70" />
</button>
Expand All @@ -127,12 +127,12 @@ defmodule GifmasterWeb.CoreComponents do
<.flash kind={:info} title={gettext("Success!")} flash={@flash} />
<.flash kind={:error} title={gettext("Error!")} flash={@flash} />
<.flash id="client-error" kind={:error} title={gettext("We can't find the internet")} phx-disconnected={show(".phx-client-error #client-error")} phx-connected={hide("#client-error")} hidden>
<%= gettext("Attempting to reconnect") %>
{gettext("Attempting to reconnect")}
<.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
</.flash>
<.flash id="server-error" kind={:error} title={gettext("Something went wrong!")} phx-disconnected={show(".phx-server-error #server-error")} phx-connected={hide("#server-error")} hidden>
<%= gettext("Hang in there while we get back on track") %>
{gettext("Hang in there while we get back on track")}
<.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
</.flash>
</div>
Expand Down Expand Up @@ -166,9 +166,9 @@ defmodule GifmasterWeb.CoreComponents do
~H"""
<.form :let={f} for={@for} as={@as} {@rest}>
<div class="mt-10 space-y-8 bg-white">
<%= render_slot(@inner_block, f) %>
{render_slot(@inner_block, f)}
<div :for={action <- @actions} class="mt-2 flex items-center justify-between gap-6">
<%= render_slot(action, f) %>
{render_slot(action, f)}
</div>
</div>
</.form>
Expand All @@ -194,13 +194,12 @@ defmodule GifmasterWeb.CoreComponents do
<button
type={@type}
class={[
"phx-submit-loading:opacity-75 rounded-lg bg-zinc-900 hover:bg-zinc-700 py-2 px-3",
"text-sm font-semibold leading-6 text-white active:text-white/80",
"phx-submit-loading:opacity-75 bg-gold text-white px-4 py-2",
@class
]}
{@rest}
>
<%= render_slot(@inner_block) %>
{render_slot(@inner_block)}
</button>
"""
end
Expand Down Expand Up @@ -239,7 +238,7 @@ defmodule GifmasterWeb.CoreComponents do
attr :type, :string,
default: "text",
values: ~w(checkbox color date datetime-local email file month number password
range search select tel text textarea time url week)
range search select tel text textarea time url week list)

attr :field, Phoenix.HTML.FormField, doc: "a form field struct retrieved from the form, for example: @form[:email]"

Expand Down Expand Up @@ -274,30 +273,30 @@ defmodule GifmasterWeb.CoreComponents do
<label class="flex items-center gap-4 text-sm leading-6 text-zinc-600">
<input type="hidden" name={@name} value="false" disabled={@rest[:disabled]} />
<input type="checkbox" id={@id} name={@name} value="true" checked={@checked} class="rounded border-zinc-300 text-zinc-900 focus:ring-0" {@rest} />
<%= @label %>
{@label}
</label>
<.error :for={msg <- @errors}><%= msg %></.error>
<.error :for={msg <- @errors}>{msg}</.error>
</div>
"""
end

def input(%{type: "select"} = assigns) do
~H"""
<div>
<.label for={@id}><%= @label %></.label>
<.label for={@id}>{@label}</.label>
<select id={@id} name={@name} class="mt-2 block w-full rounded-md border border-gray-300 bg-white shadow-sm focus:border-zinc-400 focus:ring-0 sm:text-sm" multiple={@multiple} {@rest}>
<option :if={@prompt} value=""><%= @prompt %></option>
<%= Phoenix.HTML.Form.options_for_select(@options, @value) %>
<option :if={@prompt} value="">{@prompt}</option>
{Phoenix.HTML.Form.options_for_select(@options, @value)}
</select>
<.error :for={msg <- @errors}><%= msg %></.error>
<.error :for={msg <- @errors}>{msg}</.error>
</div>
"""
end

def input(%{type: "textarea"} = assigns) do
~H"""
<div>
<.label for={@id}><%= @label %></.label>
<.label for={@id}>{@label}</.label>
<textarea
id={@id}
name={@name}
Expand All @@ -308,7 +307,29 @@ defmodule GifmasterWeb.CoreComponents do
]}
{@rest}
><%= Phoenix.HTML.Form.normalize_value("textarea", @value) %></textarea>
<.error :for={msg <- @errors}><%= msg %></.error>
<.error :for={msg <- @errors}>{msg}</.error>
</div>
"""
end

# All other inputs text, datetime-local, url, password, etc. are handled here...
def input(%{type: "list"} = assigns) do
~H"""
<div>
<.label for={@id}>{@label}</.label>
<input
type={@type}
name={@name}
id={@id}
value={Phoenix.HTML.Form.normalize_value(@type, @value)}
class={[
"block w-full bg-transparent border-0 border-b border-b-white/[.33] focus:border-0 focus:border-b focus:border-b-gold focus:shadow-none focus:ring-0 p-3 text-grey-100 font-semibold placeholder:text-grey-200 transition-colors text-lg leading-5",
@errors == [] && "",
@errors != [] && ""
]}
{@rest}
/>
<.error :for={msg <- @errors}>{msg}</.error>
</div>
"""
end
Expand All @@ -317,7 +338,7 @@ defmodule GifmasterWeb.CoreComponents do
def input(assigns) do
~H"""
<div>
<.label for={@id}><%= @label %></.label>
<.label for={@id}>{@label}</.label>
<input
type={@type}
name={@name}
Expand All @@ -330,7 +351,7 @@ defmodule GifmasterWeb.CoreComponents do
]}
{@rest}
/>
<.error :for={msg <- @errors}><%= msg %></.error>
<.error :for={msg <- @errors}>{msg}</.error>
</div>
"""
end
Expand All @@ -343,8 +364,8 @@ defmodule GifmasterWeb.CoreComponents do

def label(assigns) do
~H"""
<label for={@for} class="block text-sm font-semibold leading-6 text-zinc-800">
<%= render_slot(@inner_block) %>
<label for={@for} class="block text-sm leading-6 text-white">
{render_slot(@inner_block)}
</label>
"""
end
Expand All @@ -358,7 +379,7 @@ defmodule GifmasterWeb.CoreComponents do
~H"""
<p class="mt-3 flex gap-3 text-sm leading-6 text-rose-600">
<.icon name="hero-exclamation-circle-mini" class="mt-0.5 h-5 w-5 flex-none" />
<%= render_slot(@inner_block) %>
{render_slot(@inner_block)}
</p>
"""
end
Expand All @@ -377,13 +398,13 @@ defmodule GifmasterWeb.CoreComponents do
<header class={[@actions != [] && "flex items-center justify-between gap-6", @class]}>
<div>
<h1 class="text-lg font-semibold leading-8 text-zinc-800">
<%= render_slot(@inner_block) %>
{render_slot(@inner_block)}
</h1>
<p :if={@subtitle != []} class="mt-2 text-sm leading-6 text-zinc-600">
<%= render_slot(@subtitle) %>
{render_slot(@subtitle)}
</p>
</div>
<div class="flex-none"><%= render_slot(@actions) %></div>
<div class="flex-none">{render_slot(@actions)}</div>
</header>
"""
end
Expand Down Expand Up @@ -424,9 +445,9 @@ defmodule GifmasterWeb.CoreComponents do
<table class="w-[40rem] mt-11 sm:w-full">
<thead class="text-sm text-left leading-6 text-zinc-500">
<tr>
<th :for={col <- @col} class="p-0 pb-4 pr-6 font-normal"><%= col[:label] %></th>
<th :for={col <- @col} class="p-0 pb-4 pr-6 font-normal">{col[:label]}</th>
<th :if={@action != []} class="relative p-0 pb-4">
<span class="sr-only"><%= gettext("Actions") %></span>
<span class="sr-only">{gettext("Actions")}</span>
</th>
</tr>
</thead>
Expand All @@ -436,15 +457,15 @@ defmodule GifmasterWeb.CoreComponents do
<div class="block py-4 pr-6">
<span class="absolute -inset-y-px right-0 -left-4 group-hover:bg-zinc-50 sm:rounded-l-xl" />
<span class={["relative", i == 0 && "font-semibold text-zinc-900"]}>
<%= render_slot(col, @row_item.(row)) %>
{render_slot(col, @row_item.(row))}
</span>
</div>
</td>
<td :if={@action != []} class="relative w-14 p-0">
<div class="relative whitespace-nowrap py-4 text-right text-sm font-medium">
<span class="absolute -inset-y-px -right-4 left-0 group-hover:bg-zinc-50 sm:rounded-r-xl" />
<span :for={action <- @action} class="relative ml-4 font-semibold leading-6 text-zinc-900 hover:text-zinc-700">
<%= render_slot(action, @row_item.(row)) %>
{render_slot(action, @row_item.(row))}
</span>
</div>
</td>
Expand Down Expand Up @@ -474,8 +495,8 @@ defmodule GifmasterWeb.CoreComponents do
<div class="mt-14">
<dl class="-my-4 divide-y divide-zinc-100">
<div :for={item <- @item} class="flex gap-4 py-4 text-sm leading-6 sm:gap-8">
<dt class="w-1/4 flex-none text-zinc-500"><%= item.title %></dt>
<dd class="text-zinc-700"><%= render_slot(item) %></dd>
<dt class="w-1/4 flex-none text-zinc-500">{item.title}</dt>
<dd class="text-zinc-700">{render_slot(item)}</dd>
</div>
</dl>
</div>
Expand All @@ -497,7 +518,7 @@ defmodule GifmasterWeb.CoreComponents do
<div class="mt-16">
<.link navigate={@navigate} class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700">
<.icon name="hero-arrow-left-solid" class="h-3 w-3" />
<%= render_slot(@inner_block) %>
{render_slot(@inner_block)}
</.link>
</div>
"""
Expand Down
70 changes: 64 additions & 6 deletions lib/gifmaster_web/live/home_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ defmodule GifmasterWeb.HomeLive do
alias Gifmaster.Catalog.Gif
alias Phoenix.LiveView.AsyncResult

@public_domain "gems.gifmaster5000.com"

def mount(_params, _session, socket) do
socket
|> assign(
title: "Gifmaster 5000",
description: "The best gifs you ever did see",
show_gif_upload_modal: false,
uploaded_gifs: [],
get_gifs_form: to_form(%{"search" => ""}),
gif_changeset: Gif.changeset(%Gif{}),
gif_form: to_form(Gif.changeset(%Gif{})),
gifs: AsyncResult.loading()
)
|> assign_async(:gifs, fn -> {:ok, %{gifs: Catalog.get_gifs()}} end)
Expand All @@ -40,18 +43,41 @@ defmodule GifmasterWeb.HomeLive do
</div>
<.modal id="gif-upload-modal" show={@show_gif_upload_modal}>
<h2 class="text-2xl font-semibold">
Upload Gif
<h2 class="text-2xl font-semibold mb-4">
Upload
</h2>
<.form for={@gif_changeset}></.form>
<.form for={@gif_form} phx-submit="save_gif" phx-change="validate_gif">
<div class="w-full aspect-square bg-grey grid place-items-center mb-4">
<p :if={length(@uploads.gif.entries) == 0}>Preview</p>
<.live_img_preview :for={entry <- @uploads.gif.entries} :if={@uploads.gif.entries} entry={entry} class="w-full" />
</div>
<div class="h-20 border border-dashed border-gold grid place-items-center mb-4" phx-drop-target={@uploads.gif.ref}>
<.live_file_input class="block w-56" upload={@uploads.gif} required />
</div>
<p :for={err <- upload_errors(@uploads.gif)} class="text-red-500 mb-4">
{error_to_string(err)}
</p>
<div class="mb-4">
<.input label="Name" field={@gif_form[:name]} required />
</div>
<div class="relative mb-4">
<.input type="list" label="Tags" field={@gif_form[:tags]} required />
</div>
<.button type="submit">Upload</.button>
</.form>
</.modal>
"""
end

defp presign_upload(entry, socket) do
uploads = socket.assigns.uploads
bucket = "gems.gifmaster5000.com"
bucket = @public_domain
key = entry.client_name

config = %{
Expand All @@ -68,10 +94,42 @@ defmodule GifmasterWeb.HomeLive do
expires_in: :timer.hours(1)
)

meta = %{uploader: "S3", key: key, url: "http://#{bucket}.s3-#{config.region}.amazonaws.com", fields: fields}
meta = %{uploader: "S3", key: key, url: "http://#{bucket}.s3.amazonaws.com", fields: fields}
{:ok, meta, socket}
end

defp error_to_string(:too_large), do: "Too large"
defp error_to_string(:not_accepted), do: "You have selected an unacceptable file type"
defp error_to_string(:too_many_files), do: "You have selected too many files"

def handle_event("validate_gif", params, socket) do
gif_form =
%Gif{}
|> Gif.changeset(params)
|> to_form(action: :validate)

{:noreply, assign(socket, gif_form: gif_form)}
end

def handle_event("save_gif", %{"gif" => %{"name" => name}}, socket) do
filename =
consume_uploaded_entries(socket, :gif, fn %{key: key}, _entry -> key end)

socket =
case Catalog.create_gif(%{
name: name,
file: %{url: %{relative: "/#{filename}", absolute: "https://#{@public_domain}/#{filename}"}}
}) do
%Gif{} ->
put_flash(socket, :info, "Gif saved successfully.")

_error ->
put_flash(socket, :error, "Error saving gif.")
end

{:noreply, socket}
end

def handle_event("search", %{"search" => search}, socket) do
socket
|> assign(gifs: AsyncResult.loading())
Expand Down
2 changes: 1 addition & 1 deletion lib/gifmaster_web/simple_s3_upload.ex
Original file line number Diff line number Diff line change
Expand Up @@ -123,5 +123,5 @@ defmodule SimpleS3Upload do
|> String.slice(0..7)
end

defp sha256(secret, msg), do: :crypto.hmac(:sha256, secret, msg)
defp sha256(secret, msg), do: :crypto.mac(:hmac, :sha256, secret, msg)
end

0 comments on commit 06fc41a

Please sign in to comment.