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"""