Skip to content

Commit

Permalink
Can now create new space
Browse files Browse the repository at this point in the history
  • Loading branch information
zacksiri committed Feb 26, 2024
1 parent cc14cd0 commit 2839a7e
Show file tree
Hide file tree
Showing 11 changed files with 309 additions and 6 deletions.
8 changes: 8 additions & 0 deletions lib/polar/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ defmodule Polar.Accounts do
to: Space.Manager,
as: :create

defdelegate change_space(space),
to: Space.Manager,
as: :change

defdelegate change_space(space, attrs),
to: Space.Manager,
as: :change

defdelegate get_space_credential(query),
to: Space.Manager,
as: :get_credential
Expand Down
4 changes: 2 additions & 2 deletions lib/polar/accounts/space.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ defmodule Polar.Accounts.Space do

schema "spaces" do
field :name, :string
field :cdn_host, :string

belongs_to :owner, User

Expand All @@ -16,7 +15,8 @@ defmodule Polar.Accounts.Space do
@doc false
def changeset(space, attrs) do
space
|> cast(attrs, [:name, :cdn_host])
|> cast(attrs, [:name])
|> validate_required([:name])
|> unique_constraint(:name, name: :spaces_owner_id_name_index)
end
end
4 changes: 4 additions & 0 deletions lib/polar/accounts/space/manager.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ defmodule Polar.Accounts.Space.Manager do
|> Repo.insert()
end

def change(%Space{} = space, attrs \\ %{}) do
Space.changeset(space, attrs)
end

def get_credential(token: token) do
Space.Credential.scope(:active, Space.Credential)
|> Repo.get_by(token: token)
Expand Down
18 changes: 15 additions & 3 deletions lib/polar_web/components/layouts/app.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,26 @@
>
<%= gettext("Home") %>
</.link>
<.link
navigate={~p"/dashboard"}
class={
if assigns[:current_path] == ~p"/dashboard",
do: "bg-slate-900 text-white rounded-md px-3 py-2 text-sm font-medium",
else:
"rounded-md px-3 py-2 text-sm font-medium text-slate-300 hover:bg-slate-900 hover:text-white"
}
aria-current="page"
>
<%= gettext("Dashboard") %>
</.link>
</div>
</div>
</div>
<div class="hidden md:block">
<div class="ml-4 flex items-center md:ml-6">
<%= if @current_user do %>
<.link
href={~p"/users/settings"}
navigate={~p"/users/settings"}
class={
if assigns[:current_path] == ~p"/users/settings",
do: "bg-slate-900 text-white rounded-md px-3 py-2 text-sm font-medium",
Expand All @@ -47,13 +59,13 @@
</.link>
<% else %>
<.link
href={~p"/users/log_in"}
navigate={~p"/users/log_in"}
class="text-slate-300 rounded-md px-3 py-2 text-sm font-medium hover:bg-slate-900 hover:text-white"
>
<%= gettext("Sign In") %>
</.link>
<.link
href={~p"/users/register"}
navigate={~p"/users/register"}
class="text-slate-300 rounded-md px-3 py-2 text-sm font-medium hover:bg-slate-900 hover:text-white"
>
<%= gettext("Register") %>
Expand Down
16 changes: 16 additions & 0 deletions lib/polar_web/live/dashboard/data_loader.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
defmodule PolarWeb.Dashboard.DataLoader do
alias Polar.Repo
alias Polar.Accounts.Space

import Ecto.Query, only: [from: 2]

def load_spaces(user) do
from(s in Space,
where: s.owner_id == ^user.id,
limit: 5,
order_by: [desc: :updated_at]
)
|> Repo.all()
|> Repo.preload([:owner])
end
end
102 changes: 102 additions & 0 deletions lib/polar_web/live/dashboard/space/new_live.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
defmodule PolarWeb.Dashboard.Space.NewLive do
use PolarWeb, :live_view

alias Polar.Accounts
alias Polar.Accounts.Space

def render(assigns) do
~H"""
<div class="space-y-10 divide-y divide-gray-900/10">
<div class="grid grid-cols-1 gap-x-8 gap-y-8 md:grid-cols-3">
<div class="px-4 sm:px-0">
<h2 class="text-base font-semibold leading-7 text-slate-200">
<%= gettext("Create a space") %>
</h2>
<p class="mt-1 text-sm leading-6 text-slate-400">
<%= gettext(
"Spaces encapsulate all your tokens, it can represent an organization or just a workspace."
) %>
</p>
</div>
<.simple_form
for={@space_form}
id="new-space-form"
phx-submit="save"
phx-change="validate"
class="bg-white shadow-sm ring-1 ring-slate-900/5 sm:rounded-xl md:col-span-2"
>
<div class="px-4 py-6 sm:p-8">
<div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
<div class="sm:col-span-4">
<.input field={@space_form[:name]} label={gettext("Name")} required />
</div>
</div>
</div>
<:actions>
<div class="flex items-center justify-end gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8">
<.link navigate={~p"/dashboard"} class="text-sm font-semibold leading-6 text-gray-900">
Cancel
</.link>
<.button
type="submit"
class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
<%= gettext("Save") %>
</.button>
</div>
</:actions>
</.simple_form>
</div>
</div>
"""
end

def mount(_params, _session, socket) do
space_form = to_form(Accounts.change_space(%Space{}))

socket =
socket
|> assign(:space_form, space_form)
|> assign(:page_title, gettext("New space"))
|> assign(:current_path, ~p"/dashboard")

{:ok, socket}
end

def handle_event("validate", %{"space" => space_params} = params, %{assigns: assigns} = socket) do
space_form =
%Space{owner_id: assigns.current_user.id}
|> Accounts.change_space(space_params)
|> Map.put(:action, :validate)
|> to_form()

socket =
socket
|> assign(:space_form, space_form)

{:noreply, socket}
end

def handle_event("save", %{"space" => space_params}, %{assigns: assigns} = socket) do
case Accounts.create_space(assigns.current_user, space_params) do
{:ok, space} ->
socket =
socket
|> put_flash(:info, gettext("Space successfully created!"))
|> push_navigate(to: ~p"/dashboard")

{:noreply, socket}

{:error, %Ecto.Changeset{} = changeset} ->
space_form = to_form(changeset)

socket =
socket
|> assign(:check_errors, true)
|> assign(:space_form, space_form)

{:noreply, socket}
end
end
end
41 changes: 41 additions & 0 deletions lib/polar_web/live/dashboard/space_live.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
defmodule PolarWeb.Dashboard.SpaceLive do
use PolarWeb, :live_view

alias Polar.Repo
alias Polar.Accounts.Space

def render(assigns) do
~H"""
<div class="overflow-hidden bg-white sm:rounded-lg sm:shadow">
<div class="border-b border-gray-200 bg-white px-4 py-5 sm:px-6">
<div class="-ml-4 -mt-2 flex flex-wrap items-center justify-between sm:flex-nowrap">
<div class="ml-4 mt-2">
<h3 class="text-base font-semibold leading-6 text-gray-900">
<%= gettext("Credentials") %>
</h3>
</div>
</div>
</div>
</div>
"""
end

def mount(%{"id" => id}, _session, %{assigns: assigns} = socket) do
space = Repo.get_by(Space, owner_id: assigns.current_user.id, id: id)

if space do
socket =
socket
|> assign(:page_title, space.name)

{:ok, socket}
else
socket =
socket
|> put_flash(:error, gettext("Space not found"))
|> push_navigate(to: ~p"/dashboard")

{:ok, socket}
end
end
end
106 changes: 106 additions & 0 deletions lib/polar_web/live/dashboard_live.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
defmodule PolarWeb.DashboardLive do
use PolarWeb, :live_view

import PolarWeb.Dashboard.DataLoader

def render(assigns) do
~H"""
<div class="overflow-hidden bg-white sm:rounded-lg sm:shadow">
<div class="border-b border-gray-200 bg-white px-4 py-5 sm:px-6">
<div class="-ml-4 -mt-2 flex flex-wrap items-center justify-between sm:flex-nowrap">
<div class="ml-4 mt-2">
<h3 class="text-base font-semibold leading-6 text-gray-900">
<%= gettext("Your spaces") %>
</h3>
</div>
<div :if={Enum.count(@spaces) > 0} class="ml-4 mt-2 flex-shrink-0">
<.link
navigate={~p"/dashboard/spaces/new"}
class="relative inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
<.icon name="hero-plus-solid" class="-ml-0.5 mr-1.5 h-5 w-5 text-white" />
<%= gettext("Create new space") %>
</.link>
</div>
</div>
</div>
<div :if={Enum.count(@spaces) == 0} class="px-4 py-12">
<div class="text-center">
<svg
class="mx-auto h-12 w-12 text-gray-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
vector-effect="non-scaling-stroke"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z"
/>
</svg>
<h3 class="mt-2 text-sm font-semibold text-gray-900"><%= gettext("No spaces") %></h3>
<p class="mt-1 text-sm text-gray-500">
<%= gettext("Get started by creating a new space.") %>
</p>
<div class="mt-6">
<.link
navigate={~p"/dashboard/spaces/new"}
class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
<.icon name="hero-plus-solid" class="-ml-0.5 mr-1.5 h-5 w-5 text-white" />
<%= gettext("Create new space") %>
</.link>
</div>
</div>
</div>
<ul class="divide-y divide-slate-100">
<li
:for={space <- @spaces}
class="relative flex justify-between gap-x-6 px-4 py-5 hover:bg-gray-50 sm:px-6"
>
<div class="min-w-0">
<div class="flex items-start gap-x-3">
<p class="text-sm font-semibold leading-6 text-gray-900"><%= space.name %></p>
</div>
<div class="mt-1 flex items-center gap-x-2 text-xs leading-5 text-gray-500">
<p class="whitespace-nowrap">
<%= gettext("Created on") %>
<time datetime={space.inserted_at}>
<%= Calendar.strftime(space.inserted_at, "%b, %m %Y") %>
</time>
</p>
<svg viewBox="0 0 2 2" class="h-0.5 w-0.5 fill-current">
<circle cx="1" cy="1" r="1" />
</svg>
<p class="truncate"><%= gettext("Owned by") %> <%= space.owner.email %></p>
</div>
</div>
<div class="flex flex-none items-center gap-x-4">
<.link
navigate={~p"/dashboard/spaces/#{space.id}"}
class="hidden rounded-md bg-white px-2.5 py-1.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:block"
>
<%= gettext("View space") %>
</.link>
</div>
</li>
</ul>
</div>
"""
end

def mount(_params, _session, %{assigns: assigns} = socket) do
spaces = load_spaces(assigns.current_user)

socket =
socket
|> assign(:current_path, ~p"/dashboard")
|> assign(:page_title, gettext("Dashboard"))
|> assign(:spaces, spaces)

{:ok, socket}
end
end
2 changes: 1 addition & 1 deletion lib/polar_web/live/user_settings_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule PolarWeb.UserSettingsLive do

def render(assigns) do
~H"""
<div class="space-y-10 divide-y divide-slate-900/10">
<div class="space-y-10 divide-y divide-slate-800">
<div class="grid grid-cols-1 gap-x-8 gap-y-8 md:grid-cols-3">
<.header class="px-4 sm:px-0">
<h2 class="text-base font-semibold leading-7 text-slate-200">
Expand Down
5 changes: 5 additions & 0 deletions lib/polar_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ defmodule PolarWeb.Router do

live_session :require_authenticated_user,
on_mount: [{PolarWeb.UserAuth, :ensure_authenticated}] do
live "/dashboard", DashboardLive, :show

live "/dashboard/spaces/new", Dashboard.Space.NewLive, :new
live "/dashboard/spaces/:id", Dashboard.SpaceLive, :show

live "/users/settings", UserSettingsLive, :edit
live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule Polar.Repo.Migrations.RemoveCDNHostFromSpaces do
use Ecto.Migration

def change do
alter table(:spaces) do
remove :cdn_host
end
end
end

0 comments on commit 2839a7e

Please sign in to comment.