diff --git a/.gitignore b/.gitignore index 446641c57..c37cce96c 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,3 @@ npm-debug.log .env.* .env - diff --git a/lib/safira/accounts.ex b/lib/safira/accounts.ex index e0c86917c..98a75502e 100644 --- a/lib/safira/accounts.ex +++ b/lib/safira/accounts.ex @@ -6,6 +6,7 @@ defmodule Safira.Accounts do use Safira.Context alias Safira.Accounts.{Attendee, Course, Credential, Staff, User, UserNotifier, UserToken} + alias Safira.Companies.Company alias Safira.Contest ## Database getters @@ -83,6 +84,16 @@ defmodule Safira.Accounts do |> Repo.one() end + @doc """ + Gets a single company by user id. + """ + def get_user_company(user_id, opts \\ []) do + Company + |> where(user_id: ^user_id) + |> apply_filters(opts) + |> Repo.one() + end + @doc """ Gets a single staff. """ @@ -98,6 +109,7 @@ defmodule Safira.Accounts do user.type, case user.type do :attendee -> get_user_attendee(user.id) + :company -> get_user_company(user.id, preloads: [:tier]) :staff -> get_user_staff(user.id, preloads: [:role]) end ) diff --git a/lib/safira/accounts/user.ex b/lib/safira/accounts/user.ex index 07d85098f..253754beb 100644 --- a/lib/safira/accounts/user.ex +++ b/lib/safira/accounts/user.ex @@ -6,6 +6,7 @@ defmodule Safira.Accounts.User do alias Safira.Accounts.Attendee alias Safira.Accounts.Staff + alias Safira.Companies.Company @required_fields ~w(name email handle password type)a @optional_fields ~w(confirmed_at allows_marketing)a @@ -39,11 +40,12 @@ defmodule Safira.Accounts.User do field :hashed_password, :string, redact: true field :current_password, :string, virtual: true, redact: true field :confirmed_at, :utc_datetime - field :type, Ecto.Enum, values: [:attendee, :staff], default: :attendee + field :type, Ecto.Enum, values: [:attendee, :staff, :company], default: :attendee field :allows_marketing, :boolean, default: false has_one :attendee, Attendee, on_delete: :delete_all has_one :staff, Staff, on_delete: :delete_all + has_one :company, Company, on_delete: :delete_all timestamps(type: :utc_datetime) end @@ -81,6 +83,28 @@ defmodule Safira.Accounts.User do |> cast_assoc(:attendee, with: &Attendee.changeset/2) end + @doc """ + A user changeset for changing the profile (name, handle, password and email). + """ + def profile_changeset(user, attrs, opts \\ []) do + user + |> cast(attrs, [:name, :handle, :email, :confirmed_at, :type]) + |> unique_constraint(:email) + |> validate_handle() + |> if_changed_password_changeset(attrs, opts) + end + + defp if_changed_password_changeset(changeset, attrs, opts) do + password = Map.get(attrs, "password") + password_exists? = password != nil && String.trim(password) != "" + + if password_exists? do + password_changeset(changeset, attrs, opts) + else + changeset + end + end + defp validate_email(changeset, opts) do changeset |> validate_required([:email]) diff --git a/lib/safira/companies.ex b/lib/safira/companies.ex index fcba5f0e1..bff2d64bf 100644 --- a/lib/safira/companies.ex +++ b/lib/safira/companies.ex @@ -5,6 +5,7 @@ defmodule Safira.Companies do use Safira.Context + alias Safira.Accounts.User alias Safira.Companies.{Company, Tier} alias Safira.Spotlights.Spotlight @@ -71,7 +72,11 @@ defmodule Safira.Companies do ** (Ecto.NoResultsError) """ - def get_company!(id), do: Repo.get!(Company, id) + def get_company!(id) do + Company + |> Repo.get_by!(id: id) + |> Repo.preload([:user]) + end @doc """ Creates a company. @@ -91,6 +96,27 @@ defmodule Safira.Companies do |> Repo.insert() end + def upsert_company_and_user(company \\ %Company{}, attrs \\ %{}) do + attrs_user = Map.put(attrs["user"], "confirmed_at", DateTime.utc_now()) + company_user = if is_nil(company.user_id), do: %User{}, else: company.user + + case Ecto.Multi.new() + |> Ecto.Multi.insert_or_update( + :user, + User.profile_changeset(company_user, Map.put(attrs_user, "type", "company")) + ) + |> Ecto.Multi.insert_or_update(:company, fn %{user: user} -> + Company.changeset(company, Map.put(Map.delete(attrs, "user"), "user_id", user.id)) + end) + |> Repo.transaction() do + {:ok, %{user: user, company: company}} -> + {:ok, %{user: user, company: company}} + + {:error, failed_operation, failed_value, changes_so_far} -> + {:error, failed_operation, failed_value, changes_so_far} + end + end + @doc """ Updates a company. diff --git a/lib/safira/companies/company.ex b/lib/safira/companies/company.ex index 75c82cf5d..3da03a02f 100644 --- a/lib/safira/companies/company.ex +++ b/lib/safira/companies/company.ex @@ -4,6 +4,8 @@ defmodule Safira.Companies.Company do """ use Safira.Schema + alias Safira.Accounts.User + @derive { Flop.Schema, filterable: [:name], @@ -20,13 +22,14 @@ defmodule Safira.Companies.Company do } @required_fields ~w(name tier_id)a - @optional_fields ~w(badge_id url)a + @optional_fields ~w(badge_id user_id url)a schema "companies" do field :name, :string field :url, :string field :logo, Uploaders.Company.Type + belongs_to :user, User belongs_to :badge, Safira.Contest.Badge belongs_to :tier, Safira.Companies.Tier @@ -38,11 +41,14 @@ defmodule Safira.Companies.Company do def changeset(company, attrs) do company |> cast(attrs, @required_fields ++ @optional_fields) + |> validate_required(@required_fields) |> unique_constraint(:badge_id) + |> unique_constraint(:user_id) + |> cast_assoc(:user, with: &User.profile_changeset/2) |> cast_assoc(:badge) |> cast_assoc(:tier) - |> validate_required(@required_fields) |> validate_url(:url) + |> unsafe_validate_unique(:badge_id, Safira.Repo) end @doc false diff --git a/lib/safira/companies/tier.ex b/lib/safira/companies/tier.ex index 7def638db..582bccbe4 100644 --- a/lib/safira/companies/tier.ex +++ b/lib/safira/companies/tier.ex @@ -5,6 +5,7 @@ defmodule Safira.Companies.Tier do use Safira.Schema @required_fields ~w(name priority)a + @optional_fields ~w(full_cv_access)a @derive {Flop.Schema, sortable: [:priority], filterable: []} @@ -13,6 +14,7 @@ defmodule Safira.Companies.Tier do field :priority, :integer field :spotlight_multiplier, :float, default: 0.0 field :max_spotlights, :integer, default: 1 + field :full_cv_access, :boolean, default: false has_many :companies, Safira.Companies.Company, foreign_key: :tier_id @@ -22,7 +24,7 @@ defmodule Safira.Companies.Tier do @doc false def changeset(tier, attrs) do tier - |> cast(attrs, @required_fields) + |> cast(attrs, @required_fields ++ @optional_fields) |> validate_required(@required_fields) end diff --git a/lib/safira/contest.ex b/lib/safira/contest.ex index 5af326ef8..21fd81077 100644 --- a/lib/safira/contest.ex +++ b/lib/safira/contest.ex @@ -120,6 +120,25 @@ defmodule Safira.Contest do |> Repo.all() end + @doc """ + Lists all badge redeems belonging to a badge. + + ## Examples + + iex> list_badge_redeems(123) + {:ok, {[%BadgeRedeem{}, %BadgeRedeem{}], meta}} + + """ + def list_badge_redeems_meta(badge_id, params \\ %{}, opts \\ []) do + BadgeRedeem + |> where([br], br.badge_id == ^badge_id) + |> join(:inner, [br], a in assoc(br, :attendee), as: :attendee) + |> join(:inner, [br, a], u in assoc(a, :user), as: :user) + |> preload(attendee: [:user]) + |> apply_filters(opts) + |> Flop.validate_and_run(params, for: BadgeRedeem) + end + @doc """ Counts the number of badge redeems for a badge. diff --git a/lib/safira/contest/badge_redeem.ex b/lib/safira/contest/badge_redeem.ex index ae6e8217f..423f7c072 100644 --- a/lib/safira/contest/badge_redeem.ex +++ b/lib/safira/contest/badge_redeem.ex @@ -10,6 +10,14 @@ defmodule Safira.Contest.BadgeRedeem do @required_fields ~w(badge_id attendee_id)a @optional_fields ~w(redeemed_by_id)a + @derive {Flop.Schema, + filterable: [:badge_id, :attendee_id, :redeemed_by_id, :name], + sortable: [:inserted_at, :updated_at], + default_limit: 10, + max_limit: 50, + join_fields: [ + name: [binding: :user, field: :name] + ]} schema "badge_redeems" do belongs_to :badge, Badge diff --git a/lib/safira_web.ex b/lib/safira_web.ex index 6dd077d15..d3cd86d65 100644 --- a/lib/safira_web.ex +++ b/lib/safira_web.ex @@ -85,6 +85,17 @@ defmodule SafiraWeb do end end + def sponsor_view do + quote do + use Phoenix.LiveView, + layout: {SafiraWeb.Layouts, :sponsor} + + import SafiraWeb.Components.Button + + unquote(html_helpers()) + end + end + def backoffice_view do quote do use Phoenix.LiveView, diff --git a/lib/safira_web/components/layouts/sponsor.html.heex b/lib/safira_web/components/layouts/sponsor.html.heex new file mode 100644 index 000000000..d8e360e57 --- /dev/null +++ b/lib/safira_web/components/layouts/sponsor.html.heex @@ -0,0 +1,34 @@ +
+ <.sidebar + current_user={@current_user} + pages={SafiraWeb.Config.sponsor_pages()} + current_page={Map.get(assigns, :current_page, nil)} + logo_images={%{light: "/images/sei-logo.svg", dark: "/images/sei-logo.svg"}} + background="bg-light dark:bg-dark" + border="border-lightShade dark:border-darkShade" + logo_padding="px-8 pt-8 pb-4 invert" + user_dropdown_name_color="text-dark dark:text-light" + user_dropdown_handle_color="text-darkMuted dark:text-lightMuted" + user_dropdown_icon_color="text-lightShade dark:text-darkShade" + link_class="px-3 dark:hover:bg-darkShade group flex items-center py-2 text-sm font-medium rounded-md transition-colors" + link_active_class="bg-dark text-light hover:bg-darkShade dark:bg-darkShade" + link_inactive_class="text-dark hover:bg-lightShade/40 dark:text-light" + /> +
+
+ +
+
+
+ <.flash_group flash={@flash} /> + <%= @inner_content %> +
+
+
+
diff --git a/lib/safira_web/components/table.ex b/lib/safira_web/components/table.ex index c67b5a3cb..1b81a31b8 100644 --- a/lib/safira_web/components/table.ex +++ b/lib/safira_web/components/table.ex @@ -158,7 +158,7 @@ defmodule SafiraWeb.Components.Table do attr :meta, Flop.Meta, required: true attr :params, :map, required: true - defp pagination(assigns) do + def pagination(assigns) do ~H"""