diff --git a/lib/ares/companies.ex b/lib/ares/companies.ex
new file mode 100644
index 00000000..e44037b6
--- /dev/null
+++ b/lib/ares/companies.ex
@@ -0,0 +1,140 @@
+defmodule Ares.Companies do
+ @moduledoc """
+ The Companies context.
+ """
+
+ import Ecto.Query, warn: false
+ alias Ares.Repo
+
+ alias Ares.Companies.Company
+
+ @doc """
+ Returns the list of companies.
+
+ ## Examples
+
+ iex> list_companies()
+ [%Company{}, ...]
+
+ """
+ def list_companies do
+ Repo.all(Company)
+ end
+
+ @doc """
+ Gets a single company.
+
+ Raises `Ecto.NoResultsError` if the Company does not exist.
+
+ ## Examples
+
+ iex> get_company!(123)
+ %Company{}
+
+ iex> get_company!(456)
+ ** (Ecto.NoResultsError)
+
+ """
+ def get_company!(id), do: Repo.get!(Company, id)
+
+ @doc """
+ Creates a company.
+
+ ## Examples
+
+ iex> create_company(%{field: value})
+ {:ok, %Company{}}
+
+ iex> create_company(%{field: bad_value})
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def create_company(attrs) do
+ %Company{}
+ |> Company.changeset(attrs)
+ |> Repo.insert()
+ end
+
+ @doc """
+ Updates a company.
+
+ ## Examples
+
+ iex> update_company(company, %{field: new_value})
+ {:ok, %Company{}}
+
+ iex> update_company(company, %{field: bad_value})
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def update_company(%Company{} = company, attrs) do
+ company
+ |> Company.changeset(attrs)
+ |> Repo.update()
+ end
+
+ @doc """
+ Updates a company logo.
+
+ ## Examples
+
+ iex> update_company_logo(company, %{logo: image})
+ {:ok, %Company{}}
+
+ iex> update_company_logo(company, %{logo: bad_image})
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def update_company_logo(%Company{} = company, attrs) do
+ company
+ |> Company.image_changeset(attrs)
+ |> Repo.update()
+ end
+
+ @doc """
+ Creates or updates a company.
+
+ ## Examples
+
+ iex> create_or_update_company(%Company{}, %{field: value})
+ {:ok, %Company{}}
+
+ iex> create_or_update_company(%Company{}, %{field: bad_value})
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def create_or_update_company(%Company{} = company, attrs) do
+ company
+ |> Company.changeset(attrs)
+ |> Repo.insert_or_update()
+ end
+
+ @doc """
+ Deletes a company.
+
+ ## Examples
+
+ iex> delete_company(company)
+ {:ok, %Company{}}
+
+ iex> delete_company(company)
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def delete_company(%Company{} = company) do
+ Repo.delete(company)
+ end
+
+ @doc """
+ Returns an `%Ecto.Changeset{}` for tracking company changes.
+
+ ## Examples
+
+ iex> change_company(company)
+ %Ecto.Changeset{data: %Company{}}
+
+ """
+ def change_company(%Company{} = company, attrs \\ %{}) do
+ Company.changeset(company, attrs)
+ end
+end
diff --git a/lib/ares/companies/company.ex b/lib/ares/companies/company.ex
new file mode 100644
index 00000000..6d0bc486
--- /dev/null
+++ b/lib/ares/companies/company.ex
@@ -0,0 +1,35 @@
+defmodule Ares.Companies.Company do
+ @moduledoc """
+ Represents companies sponsoring the event.
+ """
+ use Ecto.Schema
+ use Waffle.Ecto.Schema
+ import Ecto.Changeset
+
+ @required_fields ~w(name)a
+ @optional_fields ~w(url type)a
+
+ @primary_key {:id, :binary_id, autogenerate: true}
+ @foreign_key_type :binary_id
+ schema "companies" do
+ field :name, :string
+ field :url, :string
+ field :logo, Ares.Uploaders.Company.Type
+ field :type, Ecto.Enum, values: [:sponsor, :partner], default: :sponsor
+
+ timestamps(type: :utc_datetime)
+ end
+
+ @doc false
+ def changeset(company, attrs) do
+ company
+ |> cast(attrs, @required_fields ++ @optional_fields)
+ |> validate_required(@required_fields)
+ end
+
+ @doc false
+ def image_changeset(company, attrs) do
+ company
+ |> cast_attachments(attrs, [:logo])
+ end
+end
diff --git a/lib/ares/uploaders/company.ex b/lib/ares/uploaders/company.ex
new file mode 100644
index 00000000..eee4ac92
--- /dev/null
+++ b/lib/ares/uploaders/company.ex
@@ -0,0 +1,28 @@
+defmodule Ares.Uploaders.Company do
+ @moduledoc """
+ Company image uploader.
+ """
+ use Ares.Uploader
+
+ alias Ares.Companies.Company
+
+ @versions [:original]
+ @extension_whitelist ~w(.svg .png)
+
+ def validate({file, _}) do
+ file_extension = file.file_name |> Path.extname() |> String.downcase()
+ Enum.member?(extension_whitelist(), file_extension)
+ end
+
+ def storage_dir(_, {_file, %Company{} = company}) do
+ "uploads/companies/company/#{company.id}"
+ end
+
+ def filename(version, _) do
+ version
+ end
+
+ def extension_whitelist do
+ @extension_whitelist
+ end
+end
diff --git a/lib/ares_web/components/image_uploader.ex b/lib/ares_web/components/image_uploader.ex
new file mode 100644
index 00000000..34a969ad
--- /dev/null
+++ b/lib/ares_web/components/image_uploader.ex
@@ -0,0 +1,80 @@
+defmodule AresWeb.Components.ImageUploader do
+ @moduledoc """
+ Image uploader component.
+ """
+ use AresWeb, :html
+
+ attr :upload, :any, required: true
+ attr :class, :string, default: ""
+ attr :image_class, :string, default: ""
+ attr :image, :string, default: nil
+ attr :icon, :string, default: "hero-photo"
+ attr :preview_disabled, :boolean, default: false
+ attr :rounded, :boolean, default: false
+
+ slot :placeholder, required: false, doc: "Slot for the placeholder content."
+
+ def image_uploader(assigns) do
+ ~H"""
+ <.live_file_input upload={@upload} class="hidden" />
+
+ <%= if @upload.entries == [] do %>
+
+
+ <%= if @image do %>
+
+ <% else %>
+ <%= if @placeholder do %>
+ {render_slot(@placeholder)}
+ <% else %>
+
+ <.icon name={@icon} class="w-12 h-12" />
+
{gettext("Upload a file or drag and drop.")}
+
+ <% end %>
+ <% end %>
+
+
+ <% end %>
+ <%= if !@preview_disabled do %>
+ <%= for entry <- @upload.entries do %>
+
+
+ <%= if image_file?(entry) do %>
+ <.live_img_preview
+ class={[@rounded && "p-0", not @rounded && "p-4", @image_class]}
+ entry={entry}
+ />
+ <% else %>
+
+ <.icon name="hero-document" class="w-12 h-12" />
+
{entry.client_name}
+
+ <% end %>
+
+ <%= for err <- upload_errors(@upload, entry) do %>
+ {Phoenix.Naming.humanize(err)}
+ <% end %>
+
+ <% end %>
+ <% end %>
+ <%= for err <- upload_errors(@upload) do %>
+ {Phoenix.Naming.humanize(err)}
+ <% end %>
+
+ """
+ end
+
+ defp image_file?(entry) do
+ entry.client_type in ["image/jpeg", "image/png", "image/gif"]
+ end
+end
diff --git a/lib/ares_web/components/sidebar.ex b/lib/ares_web/components/sidebar.ex
index 8e23faa8..c5e1f4e2 100644
--- a/lib/ares_web/components/sidebar.ex
+++ b/lib/ares_web/components/sidebar.ex
@@ -40,6 +40,14 @@ defmodule AresWeb.Components.Sidebar do
Event Settings
+
+ <.link
+ class="block w-full text-left py-3 px-6 hover:bg-white/10 hover:text-white transition-colors duration-150 rounded"
+ navigate="/backoffice/companies"
+ >
+ Companies
+
+