diff --git a/assets/tailwind.config.js b/assets/tailwind.config.js index e5ffe65ff..191b85f9c 100644 --- a/assets/tailwind.config.js +++ b/assets/tailwind.config.js @@ -117,7 +117,7 @@ module.exports = { }, { values }) }), - // Embeds Tabler icons (https://tablericons.com) into app.css bundle + // Embeds Tabler icons (https://tabler.io/icons) into app.css bundle plugin(function ({ matchComponents, theme }) { let iconsDir = path.join(__dirname, "../deps/tabler_icons/icons") let values = {} diff --git a/lib/atomic/departments.ex b/lib/atomic/departments.ex index 32be471ad..d0b7adb0a 100644 --- a/lib/atomic/departments.ex +++ b/lib/atomic/departments.ex @@ -194,19 +194,6 @@ defmodule Atomic.Departments do Repo.all(Collaborator) end - @doc """ - Returns the list of collaborators belonging to an organization. - - ## Examples - - iex> list_collaborators_by_organization_id("99d7c9e5-4212-4f59-a097-28aaa33c2621") - [%Collaborator{}, ...] - - """ - def list_collaborators_by_organization_id(id) do - Repo.all(from p in Collaborator, where: p.organization_id == ^id) - end - @doc """ Gets a single collaborator. @@ -363,14 +350,14 @@ defmodule Atomic.Departments do ## Examples - iex> list_collaborators_by_department_id("99d7c9e5-4212-4f59-a097-28aaa33c2621") + iex> list_department_collaborators(123) [%Collaborator{}, ...] """ - def list_collaborators_by_department_id(id, opts \\ []) do + def list_department_collaborators(id, opts \\ []) do Collaborator - |> apply_filters(opts) |> where([c], c.department_id == ^id) + |> apply_filters(opts) |> Repo.all() end diff --git a/lib/atomic/location/location.ex b/lib/atomic/location/location.ex index 3b6234f43..673bd7e57 100644 --- a/lib/atomic/location/location.ex +++ b/lib/atomic/location/location.ex @@ -19,4 +19,7 @@ defmodule Atomic.Location do |> cast(attrs, @required_fields ++ @optional_fields) |> validate_required(@required_fields) end + + def link(location) when is_map_key(location, :url), do: location.url + def link(location), do: "https://www.google.com/maps/search/?api=1&query=#{location.name}" end diff --git a/lib/atomic/organizations.ex b/lib/atomic/organizations.ex index e2c58e8ae..78e8b8612 100644 --- a/lib/atomic/organizations.ex +++ b/lib/atomic/organizations.ex @@ -136,17 +136,25 @@ defmodule Atomic.Organizations do end @doc """ - Returns the list of organizations where an user is an admin or owner. + Returns the list of organizations which are connected with the user. + By default, it returns the organizations where the user is an admin or owner. ## Examples - iex> list_user_organizations(user_id) + iex> list_user_organizations(123) [%Organization{}, ...] + + iex> list_user_organizations(456) + [] + + iex> list_user_organizations(123, [:follower]) + [%Organization{}, ...] + """ - def list_user_organizations(user_id, opts \\ []) do + def list_user_organizations(user_id, roles \\ [:admin, :owner], opts \\ []) do Organization |> join(:inner, [o], m in Membership, on: m.organization_id == o.id) - |> where([o, m], m.user_id == ^user_id and m.role in [:admin, :owner]) + |> where([o, m], m.user_id == ^user_id and m.role in ^roles) |> apply_filters(opts) |> Repo.all() end @@ -256,43 +264,51 @@ defmodule Atomic.Organizations do end @doc """ - Returns the list of memberships. + Returns the list of members in an organization. + A member is someone who is connected to the organization with a role other than `:follower`. ## Examples - iex> list_memberships(%{"organization_id" => id}) - [%Organization{}, ...] - - iex> list_memberships(%{"user_id" => id}) + iex> list_memberships(123) [%Organization{}, ...] """ - def list_memberships(params, preloads \\ []) - - def list_memberships(%{"organization_id" => organization_id}, preloads) do + def list_memberships(organization_id, opts \\ []) do Membership - |> where([a], a.organization_id == ^organization_id and a.role != :follower) + |> where([m], m.organization_id == ^organization_id and m.role != :follower) + |> apply_filters(opts) |> Repo.all() - |> Repo.preload(preloads) end - def list_memberships(%{"user_id" => user_id}, preloads) do + @doc """ + Counts the number of members in an organization. + A member is someone who is connected to the organization with a role other than `:follower`. + + ## Examples + + iex> count_memberships(123) + 5 + + iex> count_memberships(456) + 0 + + """ + def count_memberships(organization_id) do Membership - |> where([a], a.user_id == ^user_id) - |> Repo.preload(preloads) - |> Repo.all() + |> where([m], m.organization_id == ^organization_id and m.role != :follower) + |> Repo.count() end @doc """ - Verifies if an user is a member of an organization. + Verifies if an user is a member of an organization. - ## Examples + ## Examples - iex> member_of?(user, organization) - true + iex> member_of?(user, organization) + true - iex> member_of?(user, organization) - false + iex> member_of?(user, organization) + false """ def member_of?(%User{} = user, %Organization{} = organization) do @@ -301,6 +317,27 @@ defmodule Atomic.Organizations do |> Repo.exists?() end + @doc """ + Checks if an user is following an organization. + + ## Examples + + iex> user_following?(123, 456) + true + + iex> user_following?(456, 789) + false + + """ + def user_following?(user_id, organization_id) do + Membership + |> where( + [m], + m.user_id == ^user_id and m.organization_id == ^organization_id and m.role == :follower + ) + |> Repo.exists?() + end + @doc """ Gets an user role in an organization. @@ -453,19 +490,7 @@ defmodule Atomic.Organizations do |> Repo.aggregate(:count, :id) end - @doc """ - Returns the amount of members in an organization. - - ## Examples - - iex> get_total_organization_members(organization_id) - 5 - - """ - def get_total_organization_members(organization_id) do - from(m in Membership, where: m.organization_id == ^organization_id) - |> Repo.aggregate(:count, :id) - end + ## Announcements @doc """ Returns the list of announcements. diff --git a/lib/atomic/organizations/membership.ex b/lib/atomic/organizations/membership.ex index 22c058eb1..29dfe52ef 100644 --- a/lib/atomic/organizations/membership.ex +++ b/lib/atomic/organizations/membership.ex @@ -9,7 +9,7 @@ defmodule Atomic.Organizations.Membership do * `admin` - The user can control the organization's departments, activities and partners. * `follower` - The user is following the organization. - This schema can be further extended to include additional roles, such as `member`. + This schema can be further extended to include additional roles, such as `member` (with even different denominations). """ use Atomic.Schema @@ -34,6 +34,19 @@ defmodule Atomic.Organizations.Membership do organization |> cast(attrs, @required_fields ++ @optional_fields) |> validate_required(@required_fields) + |> prepare_changes(&maybe_increment_follower_count/1) + end + + defp maybe_increment_follower_count(changeset) do + organization_id = get_change(changeset, :organization_id) + role = get_change(changeset, :role) + + if organization_id && role && role == :follower do + query = from Organization, where: [id: ^organization_id] + changeset.repo.update_all(query, inc: [follower_count: 1]) + end + + changeset end def roles, do: @roles diff --git a/lib/atomic/organizations/organization.ex b/lib/atomic/organizations/organization.ex index 9db7602bb..c52e61007 100644 --- a/lib/atomic/organizations/organization.ex +++ b/lib/atomic/organizations/organization.ex @@ -3,17 +3,16 @@ defmodule Atomic.Organizations.Organization do use Atomic.Schema alias Atomic.Accounts.User - alias Atomic.Location alias Atomic.Organizations.{Announcement, Department, Membership, Partner} - alias Atomic.Uploaders + alias Atomic.{Socials, Uploaders} - @required_fields ~w(name long_name description)a - @optional_fields ~w()a + @required_fields ~w(name email long_name description)a + @optional_fields ~w(location)a @derive { Flop.Schema, - filterable: [], - sortable: [:name], + filterable: [:name], + sortable: [:name, :follower_count], compound_fields: [search: [:name]], default_order: %{ order_by: [:name], @@ -23,11 +22,18 @@ defmodule Atomic.Organizations.Organization do schema "organizations" do field :name, :string + field :email, :string field :long_name, :string field :description, :string field :logo, Uploaders.Logo.Type - embeds_one :location, Location, on_replace: :delete + field :location, :string + + # field used to better track the number of followers + # can only be updated by the system and through the memberships schema + field :follower_count, :integer, default: 0 + + embeds_one :socials, Socials, on_replace: :update has_many :departments, Department, on_replace: :delete_if_exists, @@ -51,7 +57,7 @@ defmodule Atomic.Organizations.Organization do def changeset(organization, attrs) do organization |> cast(attrs, @required_fields ++ @optional_fields) - |> cast_embed(:location, with: &Location.changeset/2) + |> cast_embed(:socials, with: &Socials.changeset/2) |> validate_required(@required_fields) |> unique_constraint(:name) end diff --git a/lib/atomic/repo.ex b/lib/atomic/repo.ex index f5ed46269..85f49def5 100644 --- a/lib/atomic/repo.ex +++ b/lib/atomic/repo.ex @@ -4,4 +4,6 @@ defmodule Atomic.Repo do adapter: Ecto.Adapters.Postgres use Paginator + + def count(query), do: aggregate(query, :count) end diff --git a/lib/atomic/socials/socials.ex b/lib/atomic/socials/socials.ex index 4c82efcdc..b58af1bf5 100644 --- a/lib/atomic/socials/socials.ex +++ b/lib/atomic/socials/socials.ex @@ -1,19 +1,20 @@ defmodule Atomic.Socials do @moduledoc """ - A socials embedded struct schema. + An embedded schema for social media handles or links. + + This schema stores the information just as it is, without any processing. """ use Atomic.Schema - @optional_fields ~w(instagram facebook x youtube tiktok website)a + @optional_fields ~w(facebook instagram x linkedin website)a @derive Jason.Encoder @primary_key false embedded_schema do - field :instagram, :string field :facebook, :string + field :instagram, :string field :x, :string - field :youtube, :string - field :tiktok, :string + field :linkedin, :string field :website, :string end @@ -22,4 +23,55 @@ defmodule Atomic.Socials do |> cast(attrs, @optional_fields) |> validate_format(:website, ~r{^https?://}, message: "must start with http:// or https://") end + + def link(:facebook, handle), do: "https://facebook.com/#{handle}" + def link(:instagram, handle), do: "https://instagram.com/#{handle}" + def link(:x, handle), do: "https://x.com/#{handle}" + def link(:linkedin, handle), do: "https://linkedin.com/#{handle}" + + @doc """ + Function providing SVG icons for social media platforms with a default size of 4 tailwind units. + + ## Examples + + iex> Socials.icon(:facebook) |> raw() + ... + + iex> Socials.icon(:instagram, 6) |> raw() + ... + + """ + def icon(platform, size \\ 4) + + def icon(:facebook, size) do + """ + + """ + end + + def icon(:instagram, size) do + """ + + """ + end + + def icon(:x, size) do + """ + + """ + end + + def icon(:linkedin, size) do + """ + + """ + end end diff --git a/lib/atomic_web.ex b/lib/atomic_web.ex index 6e7292c9e..01169a362 100644 --- a/lib/atomic_web.ex +++ b/lib/atomic_web.ex @@ -106,6 +106,11 @@ defmodule AtomicWeb do import Phoenix.LiveView.Helpers import Phoenix.Component + alias Phoenix.LiveView.JS + + # Import commonly used components + unquote(components()) + # Import basic rendering functionality (render, render_layout, etc) import Phoenix.View diff --git a/lib/atomic_web/components/avatar.ex b/lib/atomic_web/components/avatar.ex index 88e1ffb22..d35a3db91 100644 --- a/lib/atomic_web/components/avatar.ex +++ b/lib/atomic_web/components/avatar.ex @@ -36,7 +36,7 @@ defmodule AtomicWeb.Components.Avatar do :light, :dark ], - doc: "Avatar color." + doc: "Background color of the avatar." attr :class, :string, default: "", doc: "Additional classes to apply to the component." diff --git a/lib/atomic_web/components/dropdown.ex b/lib/atomic_web/components/dropdown.ex index 449ecbf25..4fab96436 100644 --- a/lib/atomic_web/components/dropdown.ex +++ b/lib/atomic_web/components/dropdown.ex @@ -4,9 +4,10 @@ defmodule AtomicWeb.Components.Dropdown do """ use Phoenix.Component - import AtomicWeb.Components.Icon alias Phoenix.LiveView.JS + import AtomicWeb.Components.Icon + attr :id, :string, required: true, doc: "The id of the dropdown." attr :items, :list, default: [], doc: "The items to display in the dropdown." @@ -22,7 +23,7 @@ defmodule AtomicWeb.Components.Dropdown do def dropdown(assigns) do ~H""" -
+
{render_slot(@wrapper)}
@@ -30,21 +31,7 @@ defmodule AtomicWeb.Components.Dropdown do """ end + + defp maybe_phx_click(item, id) when is_map_key(item, :phx_click) do + if item[:value] do + JS.push(item[:phx_click], value: item[:value]) |> hide(id) + else + JS.push(item[:phx_click]) + end + end + + defp maybe_phx_click(_item, id), do: hide(id) + + defp hide(id), + do: + JS.hide( + to: "##{id}", + transition: + {"ease-in duration-75", "transform opacity-100 scale-100", + "transform opacity-0 scale-95"} + ) + + defp hide(event, id) do + event + |> JS.hide( + to: "##{id}", + transition: + {"ease-in duration-75", "transform opacity-100 scale-100", "transform opacity-0 scale-95"} + ) + end end diff --git a/lib/atomic_web/components/forms.ex b/lib/atomic_web/components/forms.ex index a6020ea07..67cbf6e3d 100644 --- a/lib/atomic_web/components/forms.ex +++ b/lib/atomic_web/components/forms.ex @@ -43,8 +43,15 @@ defmodule AtomicWeb.Components.Forms do """ attr :id, :any, default: nil, doc: "The id of the input. If not provided, it will be generated." attr :name, :any, doc: "The name of the input. If not provided, it will be generated." - attr :label, :string, doc: "The label for the input. If not provided, it will be generated." - attr :value, :any, doc: "The value of the input. If not provided, it will be generated." + + attr :label, :string, + doc: "The label for the input. If not provided, it will be generated.", + default: "" + + attr :value, :any, + doc: "The value of the input. If not provided, it will be generated.", + default: "" + attr :type, :string, default: "text", values: @input_types, doc: "The type of the input." attr :field, HTML.FormField, diff --git a/lib/atomic_web/components/icon.ex b/lib/atomic_web/components/icon.ex index 124270b59..64e4c3b44 100644 --- a/lib/atomic_web/components/icon.ex +++ b/lib/atomic_web/components/icon.ex @@ -2,7 +2,7 @@ defmodule AtomicWeb.Components.Icon do @moduledoc """ A component for rendering icons. - An icon can either be from the [Heroicons](https://heroicons.com) or [Tabler Icons](https://tablericons.com) set. + An icon can either be from the [Heroicons](https://heroicons.com) or [Tabler Icons](https://tabler.io/icons) set. """ use Phoenix.Component diff --git a/lib/atomic_web/components/page.ex b/lib/atomic_web/components/page.ex index b39ebe303..78b0bb309 100644 --- a/lib/atomic_web/components/page.ex +++ b/lib/atomic_web/components/page.ex @@ -5,28 +5,39 @@ defmodule AtomicWeb.Components.Page do use Phoenix.Component attr :title, :string, required: true, doc: "The title of the page." + attr :description, :string, required: false, default: nil, doc: "The description of the page." attr :bottom_border, :boolean, default: false, doc: "Whether to show a bottom border after the page header." + slot :header, required: false, doc: "Slot for content to be rendered as the page header." + slot :actions, required: false, doc: "Slot for actions to be rendered in the page header." slot :inner_block, required: false, doc: "Slot for the body content of the page." def page(assigns) do ~H""" + <%= render_slot(@header) %> +
-

- {@title} -

- {render_slot(@actions)} +
+

+ <%= @title %> +

+

+ <%= @description %> +

+
+
+ <%= render_slot(@actions) %> +
- {render_slot(@inner_block)}
diff --git a/lib/atomic_web/components/tabs.ex b/lib/atomic_web/components/tabs.ex index 001cf4d9d..12c733b29 100644 --- a/lib/atomic_web/components/tabs.ex +++ b/lib/atomic_web/components/tabs.ex @@ -2,16 +2,25 @@ defmodule AtomicWeb.Components.Tabs do @moduledoc false use AtomicWeb, :component - attr :class, :string, default: "", doc: "The class to apply to the tabs" attr :underline, :boolean, default: true, doc: "Whether to show a bottom border on the tabs" + attr :class, :string, default: "", doc: "The class to apply to the tabs" attr :rest, :global + slot :inner_block, required: false def tabs(assigns) do ~H""" -
- {render_slot(@inner_block)} -
+ """ end @@ -71,13 +80,13 @@ defmodule AtomicWeb.Components.Tabs do active_classes = if active, - do: "text-white bg-primary-600", - else: "text-white bg-zinc-500" + do: "text-white bg-orange-600", + else: "text-white bg-gray-500" underline_classes = if active, - do: "bg-primary-100 text-primary-600", - else: "text-zinc-500 bg-zinc-100" + do: "bg-orange-100 text-orange-600", + else: "text-gray-500 bg-gray-100" [base_classes, active_classes, underline_classes] end @@ -88,7 +97,7 @@ defmodule AtomicWeb.Components.Tabs do active_classes = if active, do: "text-white bg-primary-600", - else: "text-white bg-zinc-500" + else: "text-white bg-gray-500" [base_classes, active_classes] end diff --git a/lib/atomic_web/config.ex b/lib/atomic_web/config.ex index 36c97e160..dba55706d 100644 --- a/lib/atomic_web/config.ex +++ b/lib/atomic_web/config.ex @@ -40,7 +40,7 @@ defmodule AtomicWeb.Config do %{ key: :partners, title: "Partners", - icon: "hero-user-group", + icon: "tabler-heart-handshake", url: ~p"/organizations/#{current_organization}/partners", tabs: [] } diff --git a/lib/atomic_web/live/department_live/index.ex b/lib/atomic_web/live/department_live/index.ex index 55e050910..bb8adca7d 100644 --- a/lib/atomic_web/live/department_live/index.ex +++ b/lib/atomic_web/live/department_live/index.ex @@ -47,7 +47,7 @@ defmodule AtomicWeb.DepartmentLive.Index do |> Enum.map(fn department -> collaborators = department.id - |> Departments.list_collaborators_by_department_id( + |> Departments.list_department_collaborators( preloads: [:user], where: [accepted: true] ) @@ -62,7 +62,7 @@ defmodule AtomicWeb.DepartmentLive.Index do |> Enum.map(fn department -> collaborators = department.id - |> Departments.list_collaborators_by_department_id( + |> Departments.list_department_collaborators( preloads: [:user], where: [accepted: true] ) diff --git a/lib/atomic_web/live/department_live/show.ex b/lib/atomic_web/live/department_live/show.ex index faee63964..8a3281341 100644 --- a/lib/atomic_web/live/department_live/show.ex +++ b/lib/atomic_web/live/department_live/show.ex @@ -41,7 +41,7 @@ defmodule AtomicWeb.DepartmentLive.Show do |> assign(list_collaborators(department.id, params, has_permissions)) |> assign( :all_collaborators, - Departments.list_collaborators_by_department_id(department.id, + Departments.list_department_collaborators(department.id, preloads: [:user], where: [accepted: true] ) @@ -76,7 +76,7 @@ defmodule AtomicWeb.DepartmentLive.Show do |> assign(list_collaborators(department.id, params, has_permissions)) |> assign( :all_collaborators, - Departments.list_collaborators_by_department_id(department.id, + Departments.list_department_collaborators(department.id, preloads: [:user], where: [accepted: true] ) diff --git a/lib/atomic_web/live/department_live/show.html.heex b/lib/atomic_web/live/department_live/show.html.heex index 92311720d..c0b799d7d 100644 --- a/lib/atomic_web/live/department_live/show.html.heex +++ b/lib/atomic_web/live/department_live/show.html.heex @@ -1,215 +1,185 @@ -
- -
- <%= if @department.banner do %> - - <% else %> - <.gradient seed={@department.id} class="object-cover" /> - <% end %> -
-
-
-
-
-
- -
-

- {@department.name} -

- <.link navigate={~p"/organizations/#{@organization}"}> -

- @{@organization.name} -

- -
- - -
- <%= if @current_view == "show" do %> - - <%= if !@current_collaborator do %> - <%= if @department.collaborator_applications do %> - <.button - phx-click={ +<.page title={@page_title} description={"@#{@organization.name}"}> + <:header> +
+ <%= if @department.banner do %> + + <% else %> + <.gradient seed={@department.id} class="object-cover" /> + <% end %> +
+ + + <:actions> + + +
+ <%= if @current_view == "show" do %> + + <%= if !@current_collaborator do %> + <%= if @department.collaborator_applications do %> + <.button + phx-click={ "#{if @is_authenticated? do "collaborate" else "must-login" end}" } - color={:white} - icon="hero-user-plus-solid" - title={gettext("Collaborate")} - /> - <% end %> - <% else %> - <%= if ! @current_collaborator.accepted do %> - <.button color={:white} icon="hero-user-plus-solid" aria-label={gettext("You have requested to collaborate with this department. Please wait for the department owner to accept your request.")} disabled /> - <% end %> - <% end %> - <.dropdown - id="actions" - items={ - [ - %{ - name: gettext("Collaborators"), - navigate: ~p"/organizations/#{@organization}/departments/#{@department}?tab=collaborators", - icon: "hero-user-group" - } - ] ++ - if @has_permissions? || (@current_collaborator && @current_collaborator.accepted) do - [%{name: gettext("Edit"), navigate: ~p"/organizations/#{@organization}/departments/#{@department}/edit?#{@params}", icon: "hero-pencil"}] - else - [] - end - } - > - <:wrapper> - <.button color={:white} icon="hero-ellipsis-horizontal-solid" /> - - - <% end %> -
- - + + + + + <%= if @current_view == "show" do %> +
+
+

<%= gettext("Recent Activity") %>

+ + <.link class="mt-4 flex flex-col items-center justify-center" navigate={~p"/organizations/#{@organization.id}"}> + <.avatar class="mb-4 p-1" type={:organization} color={:white} size={:lg} name={@organization.name} src={Uploaders.Logo.url({@organization.logo, @organization}, :original)} /> +

<%= gettext("This department doesn't have any recent activity.") %>

+

<%= gettext("In the meantime, check out %{organization_name}.", organization_name: @organization.name) %>

+ +
+ + - <%= if @current_view == "show" do %> - -
- {@department.description} -
-
-
-

{gettext("Recent Activity")}

- - <.link class="mt-4 flex flex-col items-center justify-center" navigate={~p"/organizations/#{@organization}"}> - <.avatar class="mb-4 p-1" type={:organization} color={:white} size={:lg} name={@organization.name} src={Uploaders.Logo.url({@organization.logo, @organization}, :original)} /> -

{gettext("This department doesn't have any recent activity.")}

-

{gettext("In the meantime, check out %{organization_name}.", organization_name: @organization.name)}

- -
- - + <% end %> + + <%= if @current_view == "collaborators" do %> + <%= if length(@collaborators) != 0 do %> + <%= if @has_permissions? do %> + +
+

Collaborators

+
+ <.table items={@collaborators} meta={@meta} filter={[]}> + <:col :let={collaborator} label="Name" field={:string}><%= collaborator.user.name %> + <:col :let={collaborator} label="Email" field={:string}><%= collaborator.user.email %> + <:col :let={collaborator} label="Phone number" field={:string}><%= collaborator.user.phone_number %> + <:col :let={collaborator} label="Accepted" field={:string}> + + + <:col :let={collaborator}> + <%= if collaborator.accepted do %> + <.button icon="hero-pencil-solid" color={:white} full_width patch={~p"/organizations/#{@organization}/departments/#{@department}/collaborators/#{collaborator.id}/edit?#{@params}"}>Edit + <% else %> + <.button icon="hero-envelope-solid" color={:white} full_width patch={~p"/organizations/#{@organization}/departments/#{@department}/collaborators/#{collaborator.id}/edit?#{@params}"}>Review <% end %> -
- <.link :if={length(@all_collaborators) != 0} patch={~p"/organizations/#{@organization}/departments/#{@department}?tab=collaborators"} class="text-primary-500 hover:cursor-pointer hover:underline"> - {gettext("View all collaborators")} - -
+ +
- <% end %> - <%= if @current_view == "collaborators" do %> - <%= if length(@collaborators) != 0 do %> - <%= if @has_permissions? do %> - -
-

Collaborators

-
- <.table items={@collaborators} meta={@meta} filter={[]}> - <:col :let={collaborator} label="Name" field={:string}>{collaborator.user.name} - <:col :let={collaborator} label="Email" field={:string}>{collaborator.user.email} - <:col :let={collaborator} label="Phone number" field={:string}>{collaborator.user.phone_number} - <:col :let={collaborator} label="Accepted" field={:string}> - - - <:col :let={collaborator}> - <%= if collaborator.accepted do %> - <.button icon="hero-pencil-solid" color={:white} full_width patch={~p"/organizations/#{@organization}/departments/#{@department}/collaborators/#{collaborator.id}/edit?#{@params}"} /> - <% else %> - <.button icon="hero-envelope-solid" color={:white} full_width patch={~p"/organizations/#{@organization}/departments/#{@department}/collaborators/#{collaborator.id}/edit?#{@params}"}>Review - <% end %> - - + <.pagination items={@collaborators} meta={@meta} params={@params} class="flex w-full items-center justify-between border border-t-0 pt-2" /> +
+ <% else %> + +
+

Collaborators

+ <%= for collaborator <- @collaborators do %> +
+
+ <.avatar name={collaborator.user.name} /> +
+

<%= collaborator.user.name %>

+

@<%= collaborator.user.slug %>

- <.pagination items={@collaborators} meta={@meta} params={@params} class="flex w-full items-center justify-between border border-t-0 pt-2" />
- <% else %> - -
-

Collaborators

- <%= for collaborator <- @collaborators do %> -
-
- <.avatar name={collaborator.user.name} /> -
-

{collaborator.user.name}

-

@{collaborator.user.slug}

-
-
-
- <% end %> - <.pagination items={@collaborators} meta={@meta} params={@params} class="mt-2 flex w-full items-center justify-between" /> -
- <% end %> +
<% end %> - <% end %> -
-
-
-
+ <.pagination items={@collaborators} meta={@meta} params={@params} class="mt-2 flex w-full items-center justify-between" /> +
+ <% end %> + <% end %> + <% end %> + + <.modal :if={@live_action in [:edit_collaborator]} id="edit-collaborator" show on_cancel={JS.patch(~p"/organizations/#{@organization}/departments/#{@department}?#{Map.delete(@params, "collaborator_id")}")}> <.live_component module={AtomicWeb.CollaboratorLive.FormComponent} id={@collaborator.id} title={@page_title} action={@live_action} collaborator={@collaborator} department={@department} /> diff --git a/lib/atomic_web/live/home_live/index.ex b/lib/atomic_web/live/home_live/index.ex index 142486207..4fb5fef98 100644 --- a/lib/atomic_web/live/home_live/index.ex +++ b/lib/atomic_web/live/home_live/index.ex @@ -86,8 +86,8 @@ defmodule AtomicWeb.HomeLive.Index do current_user = socket.assigns.current_user %{entries: entries, metadata: metadata} = - Organizations.list_memberships(%{"user_id" => current_user.id}) - |> Enum.map(& &1.organization_id) + Organizations.list_user_organizations(current_user.id, [:follower]) + |> Enum.map(& &1.id) |> Feed.list_posts_following_paginated([]) {:noreply, diff --git a/lib/atomic_web/live/organization_live/components/about.ex b/lib/atomic_web/live/organization_live/components/about.ex new file mode 100644 index 000000000..5f036de7d --- /dev/null +++ b/lib/atomic_web/live/organization_live/components/about.ex @@ -0,0 +1,64 @@ +defmodule AtomicWeb.OrganizationLive.Components.About do + @moduledoc false + use AtomicWeb, :component + + alias Atomic.Organizations.Organization + alias Atomic.Socials + + attr :organization, Organization, required: true, doc: "the organization which about to display" + + def about(assigns) do + ~H""" +
+

<%= gettext("Description") %>

+

<%= @organization.description %>

+ +
+

<%= gettext("Location") %>

+

<%= @organization.location %>

+
+ +
+

<%= gettext("Socials") %>

+ +
    +
  • + <.link href={@organization.socials.website} target="_blank" class="group flex items-center space-x-1"> + <.icon name="hero-link" class="size-4" /> +

    <%= @organization.socials.website %>

    + +
  • + +
  • + <.link href={Socials.link(:facebook, @organization.socials.facebook)} target="_blank" class="group flex items-center space-x-1"> + <%= Socials.icon(:facebook) |> raw() %> +

    <%= @organization.socials.facebook %>

    + +
  • + +
  • + <.link href={Socials.link(:instagram, @organization.socials.instagram)} target="_blank" class="group flex items-center space-x-1"> + <%= Socials.icon(:instagram) |> raw() %> +

    <%= @organization.socials.instagram %>

    + +
  • + +
  • + <.link href={Socials.link(:x, @organization.socials.x)} target="_blank" class="group flex items-center space-x-1"> + <%= Socials.icon(:x) |> raw() %> +

    <%= @organization.socials.x %>

    + +
  • + +
  • + <.link href={Socials.link(:linkedin, @organization.socials.linkedin)} target="_blank" class="group flex items-center space-x-1"> + <%= Socials.icon(:linkedin) |> raw() %> +

    <%= @organization.socials.linkedin %>

    + +
  • +
+
+
+ """ + end +end diff --git a/lib/atomic_web/live/organization_live/components/departments_grid.ex b/lib/atomic_web/live/organization_live/components/departments_grid.ex new file mode 100644 index 000000000..569ac1e2b --- /dev/null +++ b/lib/atomic_web/live/organization_live/components/departments_grid.ex @@ -0,0 +1,42 @@ +defmodule AtomicWeb.OrganizationLive.Components.DepartmentsGrid do + @moduledoc """ + Internal organization-related component for displaying its departments. + """ + use AtomicWeb, :component + + alias Atomic.Departments + alias Atomic.Organizations.{Department, Organization} + + # FIXME: This should be a shared component + import AtomicWeb.DepartmentLive.Components.DepartmentCard + + attr :organization, Organization, + required: true, + doc: "the organization which departments to display" + + def departments_grid(assigns) do + ~H""" +
+ <%= for department <- list_departments(@organization) do %> + <.link navigate={~p"/organizations/#{@organization.id}/departments/#{department.id}"}> + <.department_card department={department} collaborators={list_department_collaborators(department)} /> + + <% end %> +
+ """ + end + + defp list_departments(%Organization{} = organization) do + Departments.list_departments_by_organization_id(organization.id, + preloads: [:organization], + where: [archived: false] + ) + end + + defp list_department_collaborators(%Department{} = department) do + Departments.list_department_collaborators(department.id, + preloads: [:user], + where: [accepted: true] + ) + end +end diff --git a/lib/atomic_web/live/organization_live/components/membership_banner.ex b/lib/atomic_web/live/organization_live/components/membership_banner.ex new file mode 100644 index 000000000..d789add3b --- /dev/null +++ b/lib/atomic_web/live/organization_live/components/membership_banner.ex @@ -0,0 +1,55 @@ +defmodule AtomicWeb.OrganizationLive.Components.MembershipBanner do + @moduledoc """ + Organization membership banner component. Displays information about the organization's membership benefits and price. + """ + use AtomicWeb, :component + + alias Atomic.Organizations.Organization + + attr :organization, Organization, + required: true, + doc: "the organization which membership banner to display" + + def membership_banner(assigns) do + ~H""" +
+
+

<%= gettext("Lifetime membership") %>

+
+

<%= gettext("What’s included") %>

+
+
+ +
    +
  • + <.icon name="hero-check" class="h-6 w-5 flex-none text-orange-600" /> +

    Access to our room facilities

    +
  • +
  • + <.icon name="hero-check" class="h-6 w-5 flex-none text-orange-600" /> +

    Free access to all activities

    +
  • +
  • + <.icon name="hero-check" class="h-6 w-5 flex-none text-orange-600" /> +

    Official member t-shirt

    +
  • +
+
+ +
+
+
+

<%= gettext("Pay once, be a member forever") %>

+

+ 10€ + EUR +

+ <.button icon="hero-banknotes" class="mt-10 text-sm"><%= gettext("Request your membership") %> +

<%= gettext("Payments should be made within our location.") %> <%= @organization.location %>

+
+
+
+
+ """ + end +end diff --git a/lib/atomic_web/live/organization_live/components/memberships_table.ex b/lib/atomic_web/live/organization_live/components/memberships_table.ex new file mode 100644 index 000000000..ee794170f --- /dev/null +++ b/lib/atomic_web/live/organization_live/components/memberships_table.ex @@ -0,0 +1,52 @@ +defmodule AtomicWeb.OrganizationLive.Components.MembershipsTable do + @moduledoc """ + Internal organization-related component for displaying its memberships in the form of a table. + """ + use AtomicWeb, :component + + import AtomicWeb.Components.Avatar + + attr :members, :list, required: true, doc: "the list of memberships to display" + + # TODO: Make use of table component? + def memberships_table(assigns) do + ~H""" +
+
+
+ + + + + + + + + + + + + + + + +
<%= gettext("Name") %><%= gettext("Role") %><%= gettext("Joined At") %>
+
+ <.avatar name={member.user.name} size={:sm} color={:light_zinc} class="ring-1 ring-white" /> +
+
<%= member.user.name %>
+
<%= member.user.email %>
+
+
+
<%= capitalize_first_letter(member.role) %><%= relative_datetime(member.inserted_at) %>
+
+
+
+ """ + end + + defp row_click(member) do + ~p"/profile/#{member.user.id}" + |> JS.navigate() + end +end diff --git a/lib/atomic_web/live/organization_live/components/organization_card.ex b/lib/atomic_web/live/organization_live/components/organization_card.ex new file mode 100644 index 000000000..7799a6a24 --- /dev/null +++ b/lib/atomic_web/live/organization_live/components/organization_card.ex @@ -0,0 +1,89 @@ +defmodule AtomicWeb.OrganizationLive.Components.OrganizationCard do + @moduledoc false + use AtomicWeb, :component + + alias Atomic.Accounts.User + alias Atomic.Organizations + alias Atomic.Organizations.Organization + alias Atomic.Uploaders + + import AtomicWeb.Components.{Avatar, Button, Gradient} + + attr :organization, Organization, required: true, doc: "The organization to display" + attr :current_user, User, required: false, default: nil, doc: "The current user, if any" + + def organization_card(assigns) do + ~H""" +
+
+ + <%!-- <%= if @organization.banner do %> + + <% else %> --%> + <.gradient seed={@organization.id} class="rounded-t-lg" /> + <%!-- <% end %> --%> +
+ +
+
+
+
+
+ <.avatar name={@organization.name} color={:white} size={:lg} class="!size-32 p-1" type={:organization} src={Uploaders.Logo.url({@organization.logo, @organization}, :original)} /> +
+
+

+ <%= @organization.name %> +

+
+ + <%!-- TODO: Maybe show button when there's no current user, but with a must login warning? --%> + <%= if @current_user do %> + <%= if Organizations.user_following?(@current_user.id, @organization.id) do %> + <.button icon="hero-star-solid"><%= gettext("Following") %> + <% else %> + <.button icon="hero-star"><%= gettext("Follow") %> + <% end %> + <% end %> +
+ +

+ <%= @organization.long_name %> +

+ +
+
    +
  • + <.icon name="hero-users" class="size-4" /> + <%= if @organization.follower_count != 1 do %> +

    + <%= @organization.follower_count %> followers +

    + <% else %> +

    + 1 follower +

    + <% end %> +
  • + +
  • + <.icon name="hero-map-pin" class="size-4" /> +

    <%= @organization.location %>

    +
  • +
+ +
+ <.link href={@organization.socials.website} target="_blank" class="flex items-center space-x-1"> + <.icon name="hero-link" class="size-4" /> +

<%= @organization.socials.website %>

+ +
+
+
+
+ """ + end +end diff --git a/lib/atomic_web/live/organization_live/form_component.ex b/lib/atomic_web/live/organization_live/form_component.ex deleted file mode 100644 index 50b492388..000000000 --- a/lib/atomic_web/live/organization_live/form_component.ex +++ /dev/null @@ -1,60 +0,0 @@ -defmodule AtomicWeb.OrganizationLive.FormComponent do - use AtomicWeb, :live_component - - alias Atomic.Organizations - - @impl true - def mount(socket) do - {:ok, socket} - end - - @impl true - def update(%{organization: organization} = assigns, socket) do - changeset = Organizations.change_organization(organization) - - {:ok, - socket - |> assign(assigns) - |> assign(:changeset, changeset)} - end - - @impl true - def handle_event("validate", %{"organization" => organization_params}, socket) do - changeset = - socket.assigns.organization - |> Organizations.change_organization(organization_params) - |> Map.put(:action, :validate) - - {:noreply, assign(socket, :changeset, changeset)} - end - - def handle_event("save", %{"organization" => organization_params}, socket) do - save_organization(socket, socket.assigns.action, organization_params) - end - - defp save_organization(socket, :edit, organization_params) do - case Organizations.update_organization(socket.assigns.organization, organization_params) do - {:ok, _organization} -> - {:noreply, - socket - |> put_flash(:info, "Organization updated successfully") - |> push_navigate(to: socket.assigns.return_to)} - - {:error, %Ecto.Changeset{} = changeset} -> - {:noreply, assign(socket, :changeset, changeset)} - end - end - - defp save_organization(socket, :new, organization_params) do - case Organizations.create_organization(organization_params) do - {:ok, _organization} -> - {:noreply, - socket - |> put_flash(:info, "Organization created successfully") - |> push_navigate(to: socket.assigns.return_to)} - - {:error, %Ecto.Changeset{} = changeset} -> - {:noreply, assign(socket, changeset: changeset)} - end - end -end diff --git a/lib/atomic_web/live/organization_live/index.ex b/lib/atomic_web/live/organization_live/index.ex index 6a7471bca..a9452c787 100644 --- a/lib/atomic_web/live/organization_live/index.ex +++ b/lib/atomic_web/live/organization_live/index.ex @@ -1,23 +1,25 @@ defmodule AtomicWeb.OrganizationLive.Index do use AtomicWeb, :live_view - import AtomicWeb.Components.Avatar - import AtomicWeb.Components.Empty - import AtomicWeb.Components.Pagination - import AtomicWeb.Components.Button - import AtomicWeb.LiveHelpers + alias Atomic.{Accounts, Organizations} - alias Atomic.Accounts - alias Atomic.Organizations + import AtomicWeb.Components.{Dropdown, Pagination, Empty, Forms} + import AtomicWeb.OrganizationLive.Components.OrganizationCard + import AtomicWeb.LiveHelpers @impl true def mount(_params, _session, socket) do - {:ok, socket} + form = to_form(%{}, as: "search") + + {:ok, + socket + |> assign(:form, form) + |> assign(:query, "")} end @impl true - def handle_params(params, _url, socket) do - organizations_with_flop = list_organizations(params) + def handle_params(params, _, socket) do + %{organizations: organizations, meta: meta} = list_organizations(params, socket.assigns.query) {:noreply, socket @@ -25,13 +27,46 @@ defmodule AtomicWeb.OrganizationLive.Index do |> assign_page_metadata(:organizations) |> assign(:current_page, :organizations) |> assign(:params, params) - |> assign(organizations_with_flop) - |> assign(:empty?, Enum.empty?(organizations_with_flop.organizations)) + |> stream(:organizations, organizations) + |> assign(:meta, meta) + |> assign(:empty?, Enum.empty?(organizations)) |> assign(:has_permissions?, has_permissions?(socket))} end - defp list_organizations(params) do - case Organizations.list_organizations(Map.put(params, "page_size", 18)) do + @impl true + def handle_event("search", %{"search" => query}, socket) do + %{organizations: organizations, meta: meta} = list_organizations(socket.assigns.params, query) + + {:noreply, + socket + |> assign(:query, query) + |> stream(:organizations, organizations, reset: true) + |> assign(:meta, meta)} + end + + @impl true + def handle_event("sort", %{"field" => field}, socket) do + %{organizations: organizations, meta: meta} = + list_organizations(socket.assigns.params, socket.assigns.query, field) + + {:noreply, + socket + |> stream(:organizations, organizations, reset: true) + |> assign(:meta, meta)} + end + + defp list_organizations(params, query, order \\ nil) do + params = Map.put(params, "page_size", 6) + + params = + Map.put(params, "filters", %{ + filters: %{field: :name, op: :ilike, value: query} + }) + + params = if order, do: Map.put(params, "order_by", [order]), else: params + params = if order, do: Map.put(params, "order_directions", [:desc]), else: params + + case Organizations.list_organizations(params) do {:ok, {organizations, meta}} -> %{organizations: organizations, meta: meta} @@ -42,7 +77,6 @@ defmodule AtomicWeb.OrganizationLive.Index do defp has_permissions?(socket) when not socket.assigns.is_authenticated?, do: false - defp has_permissions?(socket) do - Accounts.has_master_permissions?(socket.assigns.current_user.id) - end + defp has_permissions?(socket), + do: Accounts.has_master_permissions?(socket.assigns.current_user.id) end diff --git a/lib/atomic_web/live/organization_live/index.html.heex b/lib/atomic_web/live/organization_live/index.html.heex index 47cfca2d1..3556b59b0 100644 --- a/lib/atomic_web/live/organization_live/index.html.heex +++ b/lib/atomic_web/live/organization_live/index.html.heex @@ -1,10 +1,27 @@ -<.page title="Organizations"> +<.page title={@page_title}> <:actions> <%= if not @empty? and @has_permissions? do %> - <.button navigate={~p"/organizations/new"}> - {gettext("New")} + <.button navigate={~p"/organizations/new"} icon="hero-plus"> + <%= gettext("New") %> <% end %> + + <.dropdown + id="sort-organizations-menu" + orientation={:down} + items={[ + %{name: gettext("Name") <> " (A-Z)", phx_click: "sort", value: %{"field" => "name"}}, + %{name: gettext("Followers"), phx_click: "sort", value: %{"field" => "follower_count"}} + ]} + > + <:wrapper> + + + <%= if @empty? and @has_permissions? do %> @@ -12,26 +29,20 @@ <.empty_state url={~p"/organizations/new"} placeholder="organization" />
<% else %> -
- <%= for organization <- @organizations do %> - <.link navigate={~p"/organizations/#{organization.id}"}> -
- <.avatar name={organization.name} src={Uploaders.Logo.url({organization.logo, organization}, :original)} type={:organization} size={:lg} color={:light_zinc} /> -
-

- {organization.name} -

- -

- {maybe_slice_string(organization.long_name, 85)} -

-
-
- - <% end %> +
+ <.form id="search-form" for={@form} phx-change="search"> + <.field phx-mounted={JS.focus()} value={@query} type="search" name="search" placeholder={"#{gettext("Search for an organization")}..."} /> + + +
    +
  • + <.link navigate={"/organizations/#{organization.id}"}> + <.organization_card organization={organization} current_user={@current_user} /> + +
  • +
- <.pagination items={@organizations} meta={@meta} params={@params} class="mt-2 flex w-full items-center justify-between" /> + + <.pagination items={@streams.organizations} meta={@meta} params={@params} class="mt-2 flex w-full items-center justify-between" /> <% end %> diff --git a/lib/atomic_web/live/organization_live/show.ex b/lib/atomic_web/live/organization_live/show.ex index 6eb06abae..1a46d5404 100644 --- a/lib/atomic_web/live/organization_live/show.ex +++ b/lib/atomic_web/live/organization_live/show.ex @@ -1,14 +1,16 @@ defmodule AtomicWeb.OrganizationLive.Show do use AtomicWeb, :live_view - import AtomicWeb.Components.Avatar - import AtomicWeb.LiveHelpers + alias Atomic.{Accounts, Organizations, Departments, Activities} - alias Atomic.Accounts - alias Atomic.Activities - alias Atomic.Departments - alias Atomic.Organizations - alias Atomic.Uploaders.Logo + import AtomicWeb.Components.{Gradient, Tabs} + + import AtomicWeb.OrganizationLive.Components.{ + About, + DepartmentsGrid, + MembershipsTable, + MembershipBanner + } @impl true def mount(_params, _session, socket) do @@ -16,14 +18,19 @@ defmodule AtomicWeb.OrganizationLive.Show do end @impl true - def handle_params(%{"organization_id" => organization_id} = _params, _, socket) do + def handle_params(%{"id" => organization_id} = params, _, socket) do organization = Organizations.get_organization!(organization_id) + members = maybe_list_members(organization.id, params["tab"]) + member_count = Organizations.count_memberships(organization.id) {:noreply, socket |> assign(:page_title, organization.name) - |> assign_page_metadata(:organization) + |> assign(:current_page, :organization) + |> assign(:current_tab, current_tab(socket, params)) |> assign(:organization, organization) + |> assign(:members, members) + |> assign(:member_count, member_count) |> assign(:people, Organizations.list_organizations_members(organization)) |> assign(:current_page, :organizations) |> assign(:organization, organization) @@ -77,6 +84,7 @@ defmodule AtomicWeb.OrganizationLive.Show do end end + # FIXME: Notification, somehow, is not appearing @impl true def handle_event("must-login", _payload, socket) do {:noreply, @@ -95,13 +103,18 @@ defmodule AtomicWeb.OrganizationLive.Show do end end - defp maybe_put_following(socket, _organization) when not socket.assigns.is_authenticated?, - do: false - defp maybe_put_following(socket, organization) do Organizations.member_of?(socket.assigns.current_user, organization) end + defp maybe_list_members(organization_id, "members"), + do: Organizations.list_memberships(organization_id, preloads: [:user]) + + defp maybe_list_members(_organization, _tab), do: nil + + defp current_tab(_socket, params) when is_map_key(params, "tab"), do: params["tab"] + defp current_tab(_socket, _params), do: "about" + defp has_permissions?(socket, _organization_id) when not socket.assigns.is_authenticated?, do: false diff --git a/lib/atomic_web/live/organization_live/show.html.heex b/lib/atomic_web/live/organization_live/show.html.heex index 4da34766a..1cd0c6929 100644 --- a/lib/atomic_web/live/organization_live/show.html.heex +++ b/lib/atomic_web/live/organization_live/show.html.heex @@ -1,110 +1,90 @@ -
-
-
-
-
-
- <.avatar name={@organization.name} type={:organization} src={Logo.url({@organization.logo, @organization}, :original)} size={:xl} color={:light_zinc} /> -
-
-
-

- {@organization.name} -

-

- {@organization.long_name} -

-
- {@followers_count} - Followers -
-
- <%= if not @following? do %> - - <% else %> - <%= if @organization.name == "CeSIUM" do %> -
Following
- <% else %> -
-
-
-
-

Following

-
- -
-
-
    -
  • -
    -
    -

    Unfollow

    -
    -
    -
  • -
-
- <% end %> - <% end %> - <%= if @has_permissions? do %> - <.link patch={~p"/organizations/#{@organization}/edit"} class="button"> - - - <%= link to: "#", phx_click: "delete", phx_value_id: @organization.id, data: [confirm: "Are you sure?"] do %> - - <% end %> - <% end %> -
-
-
-
-
- {@organization.description} -
-
+<.page title={@page_title} description="@cesium"> + <:header> +
+ <%!-- FIXME: Add banner support --%> + <%!-- <%= if @organization.banner do %> + + <% else %> --%> + <.gradient seed={@organization.id} class="object-cover" /> + <%!-- <% end %> --%>
-
-
+ + + <:actions> + <%= if @has_permissions? do %> +

<%= gettext("Edit") %>

+ <.button icon="hero-pencil-solid" color={:white} /> + <% end %> + +

<%= gettext("Contact") %>

+ <.link href={"mailto:#{@organization.email}"} target="_blank"> + <.button icon="hero-envelope-solid" color={:white} /> + + + <%= if @current_user && Organizations.user_following?(@current_user.id, @organization.id) do %> + <%!-- TODO: Dropdown with unfollow option --%> + <.button icon="hero-star-solid" color={:white}><%= gettext("Following") %> + <% else %> + <%!-- TODO: Follow functionality --%> + <.button phx-click={(!@current_user && "must-login") || "follow"} icon="hero-star"><%= gettext("Follow") %> + <% end %> + + + <.tabs class="scrollbar-hide flex overflow-scroll px-4 sm:px-6 lg:px-8"> + <.link patch="?tab=about" replace={false}> + <.tab id="about-tab" active={@current_tab == "about"}> + <.icon name="hero-information-circle" class="size-5 mr-2" /> + <%= gettext("About") %> + + + + <.link patch="?tab=posts" replace={false}> + <.tab id="posts-tab" active={@current_tab == "posts"}> + <.icon name="hero-newspaper" class="size-5 mr-2" /> + <%= gettext("Posts") %> + + + + <.link patch="?tab=departments" replace={false}> + <.tab id="department-tab" active={@current_tab == "departments"}> + <.icon name="hero-cube" class="size-5 mr-2" /> + <%= gettext("Departments") %> + + -
-
-
-

- People -

-
- <%= for person <- @people do %> - <.avatar name={person.name} size={:sm} color={:light_zinc} /> - <% end %> + <.link patch="?tab=partners" replace={false}> + <.tab id="partners-tab" active={@current_tab == "partners"}> + <.icon name="tabler-heart-handshake" class="size-5 mr-2" /> + <%= gettext("Partners") %> + + + + <.link patch="?tab=members" replace={false}> + <.tab id="members-tab" active={@current_tab == "members"} number={@member_count}> + <.icon name="hero-user-group" class="size-5 mr-2" /> + <%= gettext("Members") %> + + + + +
+
+ <.about organization={@organization} /> +
+ +
+ <.departments_grid organization={@organization} /> +
+ +
+ <.membership_banner organization={@organization} /> +
+ +
+
+ + <.memberships_table members={@members} />
- {gettext("View all")}
-
+ diff --git a/lib/atomic_web/live/partner_live/show.ex b/lib/atomic_web/live/partner_live/show.ex index 522d1feb1..3eae2cd6a 100644 --- a/lib/atomic_web/live/partner_live/show.ex +++ b/lib/atomic_web/live/partner_live/show.ex @@ -4,9 +4,8 @@ defmodule AtomicWeb.PartnerLive.Show do import AtomicWeb.Components.{Avatar, Socials} import AtomicWeb.LiveHelpers - alias Atomic.Accounts - alias Atomic.Organizations - alias Atomic.Partners + alias Atomic.{Accounts, Organizations, Partners} + alias Atomic.{Location, Socials} @impl true def mount(_params, _session, socket) do @@ -32,6 +31,9 @@ defmodule AtomicWeb.PartnerLive.Show do |> assign(:has_permissions?, has_permissions?(socket, organization_id))} end + defp related_partners(current, partners), + do: Enum.filter(partners, fn partner -> partner.id != current.id end) + defp has_permissions?(socket, _organization_id) when not socket.assigns.is_authenticated?, do: false diff --git a/lib/atomic_web/live/partner_live/show.html.heex b/lib/atomic_web/live/partner_live/show.html.heex index 5833bb632..c303d2b6d 100644 --- a/lib/atomic_web/live/partner_live/show.html.heex +++ b/lib/atomic_web/live/partner_live/show.html.heex @@ -1,34 +1,27 @@ -<.page title="Partners"> - <:actions> - <%= if @has_permissions? do %> -
- <.button navigate={~p"/organizations/#{@organization}/partners/#{@partner}/edit"} icon="hero-pencil"> - {gettext("Edit Partner")} - -
- <% end %> +<.page title={@page_title}> + <:actions :if={@has_permissions?}> + <.button navigate={~p"/organizations/#{@organization}/partners/#{@partner}/edit"} icon="hero-pencil"> + <%= gettext("Edit Partner") %> + +
<.avatar color={:light_zinc} name={@partner.name} src={Uploaders.PartnerImage.url({@partner.image, @partner}, :original)} type={:company} size={:xl} />
+
-

- {@partner.name} +

+ <%= @partner.name %>

- <%= if @partner.location do %> - -
- <.icon name="hero-map-pin" class="h-5 w-5 text-zinc-400" /> - <.link class="text-blue-500" href={"https://www.google.com/maps/search/?api=1&query=#{@partner.location.name}"}> - {@partner.location.name} - -
- <% end %> + <.link :if={@partner.location} href={Location.link(@partner.location)} target="_blank" class="group flex flex-row items-center space-x-1"> + <.icon name={:map_pin} class="size-4" /> +

<%= @partner.location.name %>

+ <%= if @partner.socials do %> @@ -48,6 +41,7 @@ <% end %>
+
@@ -61,47 +55,16 @@
-
+
<.icon name="hero-signal" class="size-5 mb-2" />

Benefits

<%= Enum.map(String.split(@partner.benefits, "\n"), fn phrase -> %> - <%= if String.length(phrase) < 300 do %> - {phrase} - <% else %> - {String.slice(phrase, 0..300)} - <% end %> + <%= maybe_slice_string(phrase, 300) %> <% end) %>
- <%= if @partners do %> -
-
- <.icon name="hero-star" class="size-5 mb-2" /> -

Related Partners

-
-
- <%= for partner <- @partners |> Enum.filter(fn partner -> partner.id != @partner.id end) do %> -
- <.link href={~p"/organizations/#{@organization}/partners/#{@partner}"}> -
- <.avatar color={:light_zinc} name={partner.name} src={Uploaders.PartnerImage.url({partner.image, partner}, :original)} type={:company} size={:xs} /> -

{partner.name}

-
- <%= if partner.location do %> -
- <.icon name="hero-map-pin" class="size-5 text-zinc-400" /> - {partner.location.name} -
- <% end %> - {partner.description} - -
- <% end %> -
-
- <% end %>
diff --git a/lib/atomic_web/router.ex b/lib/atomic_web/router.ex index 8d2376c0a..da52c3ddf 100644 --- a/lib/atomic_web/router.ex +++ b/lib/atomic_web/router.ex @@ -54,11 +54,7 @@ defmodule AtomicWeb.Router do ] live_session :admin, on_mount: [{AtomicWeb.Hooks, :current_user_state}] do - live "/organizations/new", OrganizationLive.New, :new - scope "/organizations/:organization_id" do - live "/edit", OrganizationLive.Edit, :edit - scope "/activities" do pipe_through :confirm_activity_association live "/new", ActivityLive.New, :new @@ -96,14 +92,16 @@ defmodule AtomicWeb.Router do live "/", HomeLive.Index, :index live "/calendar", CalendarLive.Show, :show live "/activities", ActivityLive.Index, :index + live "/organizations", OrganizationLive.Index, :index + live "/organizations/:id", OrganizationLive.Show, :show + live "/announcements", AnnouncementLive.Index, :index live "/tos", TermsLive.Show, :show live "/privacy", PrivacyLive.Show, :show live "/cookies", CookiesLive.Show, :show live "/activities/:id", ActivityLive.Show, :show - live "/organizations/:organization_id", OrganizationLive.Show, :show live "/announcements/:id", AnnouncementLive.Show, :show live "/profile/:slug", ProfileLive.Show, :show @@ -142,10 +140,6 @@ defmodule AtomicWeb.Router do live "/:id", AnnouncementLive.Show, :show end end - - # Only masters can create organizations - pipe_through [:master] - live "/organizations/new", OrganizationLive.New, :new end end diff --git a/priv/fake/organizations.json b/priv/fake/organizations.json index 453eac32c..7a5910b97 100644 --- a/priv/fake/organizations.json +++ b/priv/fake/organizations.json @@ -1,232 +1,232 @@ [ { - "name": "ADAUM", - "long_name": "Associação de Debates Académicos da Universidade do Minho", - "description": "Associação de Debates Académicos da Universidade do Minho" + "name": "ADAUM", + "long_name": "Associação de Debates Académicos da Universidade do Minho", + "description": "A ADAUM é uma associação da Universidade do Minho que tem por fim promover o debate competitivo, bem como o espírito crítico." }, { - "name": "ADEGE", - "long_name": "Associação de Estudantes de Gestão da Universidade do Minho", - "description": "Associação de Estudantes de Gestão da Universidade do Minho" + "name": "ADEGE", + "long_name": "Associação de Estudantes de Gestão da Universidade do Minho", + "description": "A ADEGE é o órgão representativo de todos os estudantes do Licenciatura em Gestão, da Escola de Economia e Gestão da Universidade do Minho (EEG-UM)." }, { - "name": "AEDUM", - "long_name": "Associação de Estudantes de Direito da Universidade do Minho", - "description": "Associação de Estudantes de Direito da Universidade do Minho" + "name": "AEDUM", + "long_name": "Associação de Estudantes de Direito da Universidade do Minho", + "description": "A Associação de Estudantes de Direito da Universidade do Minho é uma associação de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Direito, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "AEECUM", - "long_name": "Associação de Estudantes de Engenharia Civil da Universidade do Minho", - "description": "Associação Estudantes Engenharia Civil da Universidade do Minho" + "name": "AEECUM", + "long_name": "Associação de Estudantes de Engenharia Civil da Universidade do Minho", + "description": "Associação sempre ao serviço dos estudantes de Engenharia Civil da Universidade do Minho!" }, { - "name": "AEESECG", - "long_name": "Associação de Estudantes da Escola Superior de Enfermagem Calouste Gulbenkian", - "description": "Associação de Estudantes da Escola Superior de Enfermagem Calouste Gulbenkian" + "name": "AEESECG", + "long_name": "Associação de Estudantes da Escola Superior de Enfermagem Calouste Gulbenkian", + "description": "A AEESECG tem como objectivo primordial representar e defender os interesses dos estudantes da ESE-UM. Fomentar as relações de cooperação e amizade aos antigos alunos da AEESECG, promover a formação cultural e humana da comunidade estudantil, através da dinamização de actividades cientifico-pedagógicacas, sócio-culturais, recreativas e desportivas. Além disso pretende desenvolver a cooperação e solidariedade entre os estudantes da ESE-UM, promovendo uma política de igualdade de oportunidades." }, { - "name": "AEHUM", - "long_name": "Associação de Estudantes de História da Universidade do Minho", - "description": "Associação de Estudantes da História da Universidade do Minho" + "name": "AEHUM", + "long_name": "Associação de Estudantes de História da Universidade do Minho", + "description": "A Associação de Estudantes de História da Universidade do Minho é uma associação de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de História, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "AEPUM", - "long_name": "Associação de Estudantes de Psicologia da Universidade do Minho", - "description": "Associação de Estudantes de Psicologia da Universidade do Minho" + "name": "AEPUM", + "long_name": "Associação de Estudantes de Psicologia da Universidade do Minho", + "description": "A Associação de Estudantes de Psicologia da Universidade do Minho é uma associação de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Psicologia, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "AIESEC Minho", - "long_name": "Association Internationale des Etudiants en Sciences Economiques et Commerciales – UMinho", - "description": "Association Internationale des Etudiants en Sciences Economiques et Commerciales – UMinho" + "name": "AIESEC Minho", + "long_name": "Association Internationale des Etudiants en Sciences Economiques et Commerciales – UMinho", + "description": "A Association Internationale des Etudiants en Sciences Economiques et Commerciales – UMinho é uma associação de estudantes da Universidade do Minho que tem como objetivo dotar os seus membros de competências de liderança e empreendedorismo, bem como promover a mobilidade internacional dos estudantes." }, { - "name": "AIS.SC", - "long_name": "Association for Information Systems Student Chapter", - "description": "Association for Information Systems Student Chapter" + "name": "AIS.SC", + "long_name": "Association for Information Systems Student Chapter", + "description": "A Association for Information Systems Student Chapter é uma associação de estudantes da Universidade do Minho que tem como objetivo promover a partilha de conhecimento e experiências na área dos sistemas de informação, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "A3RUM", - "long_name": "Associação de Alunos de Arqueologia da Universidade do Minho", - "description": "Associação de Alunos de Arqueologia da Universidade do Minho" + "name": "A3RUM", + "long_name": "Associação de Alunos de Arqueologia da Universidade do Minho", + "description": "A Associação de Alunos de Arqueologia da Universidade do Minho é uma associação de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Arqueologia, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "CEAP", - "long_name": "Centro de Estudos de Administração Pública", - "description": "Centro de Estudos de Administração Pública" + "name": "CEAP", + "long_name": "Centro de Estudos de Administração Pública", + "description": "O Centro de Estudos de Administração Pública é um associação de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Administração Pública, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "CECRI", - "long_name": "Centro de Estudos de Comunicação e Relações Internacionais", - "description": "Centro de Estudos do Curso de Relações Internacionais" + "name": "CECRI", + "long_name": "Centro de Estudos de Comunicação e Relações Internacionais", + "description": "O Centro de Estudos de Comunicação e Relações Internacionais é um associação de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Comunicação e Relações Internacionais, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "CeSIUM", - "long_name": "Centro de Estudantes de Engenharia Informática da Universidade do Minho", - "description": "Centro de Estudantes de Engenharia de Sistemas e Informática da Universidade do Minho" + "name": "CeSIUM", + "long_name": "Centro de Estudantes de Engenharia Informática da Universidade do Minho", + "description": "O CeSIUM é um grupo de estudantes voluntários, que tem como objetivo representar e promover o curso de Engenharia Informática 💾 na UMinho 🎓" }, { - "name": "CineFOCUM", - "long_name": "Núcleo de Cinemas da Universidade do Minho", - "description": "Núcleo de Cinema da Universidade do Minho" + "name": "CineFOCUM", + "long_name": "Núcleo de Cinema da Universidade do Minho", + "description": "O CineFOCUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a cultura cinematográfica, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "ELSA", - "long_name": "European Law Students Association", - "description": "European Law Students Association" + "name": "ELSA", + "long_name": "European Law Students Association", + "description": "ELSA UMinho é uma afiliação da ELSA Portugal, que por sua vez é uma afiliação da ELSA International. A ELSA é a maior associação de estudantes de Direito do mundo, com mais de 60,000 membros em 43 países." }, { - "name": "GAEB", - "long_name": "Grupo de Alunos de Engenharia Biomédica", - "description": "Grupo de Alunos de Engenharia Biomédica" + "name": "GAEB", + "long_name": "Grupo de Alunos de Engenharia Biomédica", + "description": "O GAEB é um grupo de alunos da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Engenharia Biomédica, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "GACCUM", - "long_name": "Grupo de Alunos de Ciências da Comunicação", - "description": "Grupo dos Alunos de Ciências da Comunicação" + "name": "GACCUM", + "long_name": "Grupo de Alunos de Ciências da Comunicação", + "description": "O Grupo de Alunos de Ciências da Comunicação é uma associação de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Ciências da Comunicação, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "GeoPlanUM", - "long_name": "Associação dos Estudantes de Geografia e Planeamento da Universidade do Minho", - "description": "Associação dos Estudantes de Geografia e Planeamento da Universidade do Minho" + "name": "GeoPlanUM", + "long_name": "Associação dos Estudantes de Geografia e Planeamento da Universidade do Minho", + "description": "O GeoPlanUM é uma associação de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Geografia e Planeamento, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "Music UM", - "long_name": "Núcleo de Estudantes de Música da Universidade do Minho", - "description": "Núcleo de Estudantes de Música da Universidade do Minho" + "name": "Music UM", + "long_name": "Núcleo de Estudantes de Música da Universidade do Minho", + "description": "O Music UM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a cultura musical, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "NAECUM", - "long_name": "Núcleo de Alunos de Economia da Universidade do Minho", - "description": "Núcleo de Alunos de Economia da Universidade do Minho" + "name": "NAECUM", + "long_name": "Núcleo de Alunos de Economia da Universidade do Minho", + "description": "O NAECUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Economia, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "NAMECUM", - "long_name": "Núcleo de Alunos de Engenharia Mecânica da Universidade do Minho", - "description": "Núcleo de Alunos de Engenharia Mecânica da Universidade do Minho" + "name": "NAMECUM", + "long_name": "Núcleo de Alunos de Engenharia Mecânica da Universidade do Minho", + "description": "O NAMECUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Engenharia Mecânica, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "NAQUM", - "long_name": "Núcleo de Alunos de Química da Universidade do Minho", - "description": "Núcleo de Alunos de Química da Universidade do Minho" + "name": "NAQUM", + "long_name": "Núcleo de Alunos de Química da Universidade do Minho", + "description": "O NAQUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Química, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "NEAUM", - "long_name": "Núcleo de Estudantes de Arquitetura da Universidade do Minho", - "description": "Núcleo de Estudantes de Arquitetura da Universidade do Minho" + "name": "NEAUM", + "long_name": "Núcleo de Estudantes de Arquitetura da Universidade do Minho", + "description": "O NEAUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Arquitetura, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "NEBAUM", - "long_name": "Núcleo de Estudantes de Biologia Aplicada da Universidade do Minho", - "description": "Núcleo de Estudantes de Biologia Aplicada da Universidade do Minho" + "name": "NEBAUM", + "long_name": "Núcleo de Estudantes de Biologia Aplicada da Universidade do Minho", + "description": "O NEBAUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Biologia Aplicada, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "NEBQUM", - "long_name": "Núcleo de Estudantes de Bioquímica da Universidade do Minho", - "description": "Núcleo de Estudantes de Bioquímica da Universidade do Minho" + "name": "NEBQUM", + "long_name": "Núcleo de Estudantes de Bioquímica da Universidade do Minho", + "description": "O NEBQUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Bioquímica, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "NECC", - "long_name": "Núcleo de Estudantes de Ciências da Comunicação da Universidade do Minho", - "description": "Núcleo de Estudantes de Ciências da Computação da Universidade do Minho" + "name": "NECC", + "long_name": "Núcleo de Estudantes de Ciências da Computação da Universidade do Minho", + "description": "O NECC é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Ciências da Computação, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "NECSUM", - "long_name": "Núcleo de Estudantes do Curso da Sociologia da Universidade do Minho", - "description": "Núcleo de Estudantes do Curso de Sociologia da Universidade do Minho" + "name": "NECSUM", + "long_name": "Núcleo de Estudantes do Curso da Sociologia da Universidade do Minho", + "description": "O NECSUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Sociologia, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "NEDUM", - "long_name": "Núcleo de Estudantes de Educação da Universidade do Minho", - "description": "Núcleo de Estudantes de Educação da Universidade do Minho" + "name": "NEDUM", + "long_name": "Núcleo de Estudantes de Educação da Universidade do Minho", + "description": "O NEDUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Educação, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "NEEB", - "long_name": "Núcleo de Estudantes de Engenharia Biológica", - "description": "Núcleo de Estudos de Engenharia Biológica" + "name": "NEEB", + "long_name": "Núcleo de Estudantes de Engenharia Biológica", + "description": "O NEEB é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Engenharia Biológica, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "NEEBUM", - "long_name": "Núcleo de Estudantes de Educação Básica da Universidade do Minho", - "description": "Núcleo de Estudantes de Educação Básica da Universidade do Minho" + "name": "NEEBUM", + "long_name": "Núcleo de Estudantes de Educação Básica da Universidade do Minho", + "description": "O NEEBUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Educação Básica, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "NEECUM", - "long_name": "Núcleo de Estudantes de Engenharia de Comunicações da Universidade do Minho", - "description": "Núcleo de Estudantes de Engenharia de Comunicações da Universidade do Minho" + "name": "NEECUM", + "long_name": "Núcleo de Estudantes de Engenharia de Comunicações da Universidade do Minho", + "description": "O NEECUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Engenharia de Comunicações, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "NEEGIUM", - "long_name": "Núcleo de Estudantes de Engenharia e Gestão Industrial da Universidade do Minho", - "description": "Núcleo de Estudantes de Engenharia e Gestão Industrial da Universidade do Minho" + "name": "NEEGIUM", + "long_name": "Núcleo de Estudantes de Engenharia e Gestão Industrial da Universidade do Minho", + "description": "O NEEGIUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Engenharia e Gestão Industrial, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "NEEUM", - "long_name": "Núcleo de Estudantes de Estatística da Universidade do Minho", - "description": "Núcleo de Estudantes de Estatística da Universidade do Minho" + "name": "NEEUM", + "long_name": "Núcleo de Estudantes de Estatística da Universidade do Minho", + "description": "O NEEUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Estatística, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "NEFILUM", - "long_name": "Núcleo de Estudantes de Filosofia da Universidade do Minho", - "description": "Núcleo de Estudantes de Filosofia da Universidade do Minho" + "name": "NEFILUM", + "long_name": "Núcleo de Estudantes de Filosofia da Universidade do Minho", + "description": "O NEFILUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Filosofia, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "NEFUM", - "long_name": "Núcleo de Estudantes de Física da Universidade do Minho", - "description": "Núcleo de Estudantes de Física da Universidade do Minho" + "name": "NEFUM", + "long_name": "Núcleo de Estudantes de Física da Universidade do Minho", + "description": "O NEFUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Física, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "NEIEEEUM", - "long_name": "Núcleo de Estudantes de Engenharia Eletrotécnica e de Computadores da Universidade do Minho", - "description": "Núcleo Estudantil do Institute of Electrical & Electronics Engineers da Universidade do Minho" + "name": "NEIEEEUM", + "long_name": "Núcleo Estudantil do Institute of Electrical & Electronics Engineers da Universidade do Minho", + "description": "O NEIEEUM é uma afiliação do Institute of Electrical & Electronics Engineers (IEEE) na Universidade do Minho." }, { - "name": "NELAUM", - "long_name": "Núcleo de Estudantes de Línguas Aplicadas da Universidade do Minho", - "description": "Núcleo de Estudantes de Línguas Aplicadas da Universidade do Minho" + "name": "NELAUM", + "long_name": "Núcleo de Estudantes de Línguas Aplicadas da Universidade do Minho", + "description": "O NELAUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Línguas Aplicadas, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "NEMUM", - "long_name": "Núcleo de Estudantes de Medicina da Universidade do Minho", - "description": "Núcleo de Estudantes de Medicina da Universidade do Minho" + "name": "NEMUM", + "long_name": "Núcleo de Estudantes de Medicina da Universidade do Minho", + "description": "O NEMUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Medicina, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "NENIUM", - "long_name": "Núcleo de Estudantes de Negócios Internacionais da Universidade do Minho", - "description": "Núcleo de Estudantes de Negócios Internacionais da Universidade do Minho" + "name": "NENIUM", + "long_name": "Núcleo de Estudantes de Negócios Internacionais da Universidade do Minho", + "description": "O NENIUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Negócios Internacionais, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "NEOUM", - "long_name": "Núcleo de Estudantes de Optometria da Universidade do Minho", - "description": "Núcleo de Estudantes de Optometria da Universidade do Minho" + "name": "NEOUM", + "long_name": "Núcleo de Estudantes de Optometria da Universidade do Minho", + "description": "O NEOUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Optometria, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "NEPLUM", - "long_name": "Núcleo de Estudantes de Estudos Portugueses e Lusófonos da Universidade do Minho", - "description": "Núcleo de Estudantes de Estudos Portugueses e Lusófonos da Universidade do Minho" + "name": "NEPLUM", + "long_name": "Núcleo de Estudantes de Estudos Portugueses e Lusófonos da Universidade do Minho", + "description": "O NEPLUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Estudos Portugueses e Lusófonos, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "NEMKT", - "long_name": "Núcleo de Estudantes de Marketing da Universidade do Minho", - "description": "Núcleo de Estudantes de Marketing da Universidade do Minho" + "name": "NEMKT", + "long_name": "Núcleo de Estudantes de Marketing da Universidade do Minho", + "description": "O NEMKT é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Marketing, bem como a realização de atividades culturais, desportivas e de lazer." }, { "name": "NUCCLEUM", "long_name": "Núcleo de Estudantes de Linguas e Literaturas Europeias da Universidade do Minho", - "description": "Núcleo de Estudantes de Linguas e Literaturas Europeias da Universidade do Minho" + "description": "O NUCCLEUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Linguas e Literaturas Europeias, bem como a realização de atividades culturais, desportivas e de lazer." }, { - "name": "Núcleo de Estudante de Contabilidade da Universidade do Minho", + "name": "NECONTUM", "long_name": "Núcleo de Estudante de Contabilidade da Universidade do Minho", - "description": "Núcleo de Estudante de Contabilidade da Universidade do Minho" + "description": "O NECONTUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Contabilidade, bem como a realização de atividades culturais, desportivas e de lazer." }, { "name": "NUMERUM", "long_name": "Núcleo de Estudantes de Matemática da Universidade do Minho", - "description": "Núcleo de Estudantes de Matemática da Universidade do Minho" + "description": "O NUMERUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Matemática, bem como a realização de atividades culturais, desportivas e de lazer." }, { "name": "PoliticUM", - "long_name": "PoliticUM", - "description": "PoliticUM" + "long_name": "Núcleo de Estudantes de Ciência Política da Universidade do Minho", + "description": "O PoliticUM é um núcleo de estudantes da Universidade do Minho que tem como objetivo promover a integração dos estudantes do curso de Ciência Política, bem como a realização de atividades culturais, desportivas e de lazer." } ] diff --git a/priv/repo/migrations/2022000000000_create_organizations.exs b/priv/repo/migrations/2022000000000_create_organizations.exs index d725bdd89..0a0795e31 100644 --- a/priv/repo/migrations/2022000000000_create_organizations.exs +++ b/priv/repo/migrations/2022000000000_create_organizations.exs @@ -6,11 +6,16 @@ defmodule Atomic.Repo.Migrations.CreateOrganizations do add :id, :binary_id, primary_key: true add :name, :string, null: false + add :email, :string, null: false add :long_name, :string, null: false add :description, :text, null: false add :logo, :string - add :location, :map + add :location, :string + + add :follower_count, :integer + + add :socials, :map timestamps() end diff --git a/priv/repo/seeds/organizations.exs b/priv/repo/seeds/organizations.exs index b0d25b2e1..dfb9c162c 100644 --- a/priv/repo/seeds/organizations.exs +++ b/priv/repo/seeds/organizations.exs @@ -23,13 +23,17 @@ defmodule Atomic.Repo.Seeds.Organizations do # Seed CeSIUM %Organization{ name: "CeSIUM", - long_name: - "CeSIUM - Centro de Estudantes de Engenharia Informática da Universidade do Minho", + email: "cesium@di.uminho.pt", + long_name: "Centro de Estudantes de Engenharia Informática da Universidade do Minho", description: "O CeSIUM é um grupo de estudantes voluntários, que tem como objetivo representar e promover o curso de Engenharia Informática 💾 na UMinho 🎓", - location: %{ - name: "Departamento de Informática, Campus de Gualtar, Universidade do Minho", - url: "https://cesium.di.uminho.pt" + location: "Edifício 7, Universidade do Minho", + socials: %{ + facebook: "cesiuminho", + instagram: "cesiuminho", + x: "cesiuminho", + linkedin: "cesiuminho", + website: "https://cesium.pt" } } |> Repo.insert!() @@ -50,8 +54,12 @@ defmodule Atomic.Repo.Seeds.Organizations do {:ok, new_org} = %{ name: organization["name"], + email: Faker.Internet.email(), long_name: organization["long_name"], - description: organization["description"] + description: organization["description"], + socials: %{ + website: Faker.Internet.url() + } } |> Organizations.create_organization() diff --git a/priv/repo/seeds/partners.exs b/priv/repo/seeds/partners.exs index 0d50bb0bd..74f4f1bd3 100644 --- a/priv/repo/seeds/partners.exs +++ b/priv/repo/seeds/partners.exs @@ -24,11 +24,10 @@ defmodule Atomic.Repo.Seeds.Partners do } socials = %{ - instagram: Faker.Internet.slug(), facebook: Faker.Internet.slug(), + instagram: Faker.Internet.slug(), x: Faker.Internet.slug(), - youtube: Faker.Internet.slug(), - tiktok: Faker.Internet.slug(), + linkedin: Faker.Internet.slug(), website: Faker.Internet.url() } diff --git a/priv/static/images/facebook.svg b/priv/static/images/facebook.svg index 92b7016c8..a3471f59c 100644 --- a/priv/static/images/facebook.svg +++ b/priv/static/images/facebook.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/priv/static/images/x.svg b/priv/static/images/x.svg index e704e9dfb..181016d4d 100644 --- a/priv/static/images/x.svg +++ b/priv/static/images/x.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/test/atomic/organizations_test.exs b/test/atomic/organizations_test.exs index a116ba5a0..b42d25f88 100644 --- a/test/atomic/organizations_test.exs +++ b/test/atomic/organizations_test.exs @@ -78,17 +78,7 @@ defmodule Atomic.OrganizationsTest do membership = insert(:membership, role: :admin) memberships = - Organizations.list_memberships(%{"organization_id" => membership.organization_id}) - |> Enum.map(& &1.id) - - assert memberships == [membership.id] - end - - test "list_memberships/1 returns all memberships of user" do - membership = insert(:membership) - - memberships = - Organizations.list_memberships(%{"user_id" => membership.user_id}) + Organizations.list_memberships(membership.organization_id) |> Enum.map(& &1.id) assert memberships == [membership.id]