Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: company main page #437

Merged
merged 24 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,3 @@ npm-debug.log

.env.*
.env

12 changes: 12 additions & 0 deletions lib/safira/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
"""
Expand All @@ -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
)
Expand Down
26 changes: 25 additions & 1 deletion lib/safira/accounts/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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])
Expand Down
28 changes: 27 additions & 1 deletion lib/safira/companies.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ defmodule Safira.Companies do

use Safira.Context

alias Safira.Accounts.User
alias Safira.Companies.{Company, Tier}
alias Safira.Spotlights.Spotlight

Expand Down Expand Up @@ -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.
Expand All @@ -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.

Expand Down
10 changes: 8 additions & 2 deletions lib/safira/companies/company.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ defmodule Safira.Companies.Company do
"""
use Safira.Schema

alias Safira.Accounts.User

@derive {
Flop.Schema,
filterable: [:name],
Expand All @@ -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

Expand All @@ -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
Expand Down
4 changes: 3 additions & 1 deletion lib/safira/companies/tier.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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: []}

Expand All @@ -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

Expand All @@ -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

Expand Down
19 changes: 19 additions & 0 deletions lib/safira/contest.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
8 changes: 8 additions & 0 deletions lib/safira/contest/badge_redeem.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
11 changes: 11 additions & 0 deletions lib/safira_web.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
34 changes: 34 additions & 0 deletions lib/safira_web/components/layouts/sponsor.html.heex
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<div class="relative h-screen flex overflow-hidden">
<.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"
/>
<div class="flex flex-col flex-1 overflow-hidden">
<div class="bg-light dark:bg-dark flex justify-end lg:hidden px-4 sm:px-6 py-2">
<button
class="sidebar-toggle flex items-center justify-center w-16 dark:text-light text-dark"
aria-expanded="false"
phx-click={show_mobile_sidebar()}
>
<.icon class="w-8 h-8" name="hero-bars-3" />
</button>
</div>
<main class="text-dark dark:text-light bg-light dark:bg-dark flex-1 relative z-0 overflow-y-auto focus:outline-none">
<div class="px-4 sm:px-6 lg:px-8 py-8">
<.flash_group flash={@flash} />
<%= @inner_content %>
</div>
</main>
</div>
</div>
2 changes: 1 addition & 1 deletion lib/safira_web/components/table.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
<nav
class="flex items-center flex-column flex-wrap md:flex-row justify-between py-4"
Expand Down
11 changes: 11 additions & 0 deletions lib/safira_web/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,17 @@ defmodule SafiraWeb.Config do
end
end

def sponsor_pages do
[
%{
key: :visitors,
title: "Visitors",
icon: "hero-user",
url: "/sponsor"
}
]
end

def backoffice_pages(user) do
permissions = user.staff.role.permissions

Expand Down
38 changes: 18 additions & 20 deletions lib/safira_web/live/backoffice/companies_live/form_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ defmodule SafiraWeb.Backoffice.CompanyLive.FormComponent do
<div class="grid grid-cols-2">
<.field field={@form[:name]} type="text" label="Name" wrapper_class="pr-2" required />
<.field field={@form[:url]} type="text" label="URL" wrapper_class="" />
<.inputs_for :let={user} field={@form[:user]}>
<.field field={user[:email]} type="email" label="Email" wrapper_class="pr-2" required />
<.field
field={user[:password]}
type="password"
label="Password"
wrapper_class=""
required={@action == :new}
/>
<.field field={user[:handle]} type="text" label="Handle" wrapper_class="pr-2" required />
<.field field={user[:name]} type="text" label="User name" wrapper_class="" required />
</.inputs_for>

<.field
field={@form[:tier_id]}
type="select"
Expand Down Expand Up @@ -83,6 +96,7 @@ defmodule SafiraWeb.Backoffice.CompanyLive.FormComponent do
{:ok,
socket
|> assign(assigns)
|> assign(:company, company)
|> assign_new(:form, fn ->
to_form(Companies.change_company(company))
end)}
Expand All @@ -95,12 +109,12 @@ defmodule SafiraWeb.Backoffice.CompanyLive.FormComponent do
end

def handle_event("save", %{"company" => company_params}, socket) do
save_company(socket, socket.assigns.action, company_params)
save_company(socket, company_params)
end

defp save_company(socket, :edit, company_params) do
case Companies.update_company(socket.assigns.company, company_params) do
{:ok, company} ->
defp save_company(socket, company_params) do
case Companies.upsert_company_and_user(socket.assigns.company, company_params) do
{:ok, %{company: company}} ->
case consume_image_data(company, socket) do
{:ok, _company} ->
{:noreply,
Expand All @@ -114,22 +128,6 @@ defmodule SafiraWeb.Backoffice.CompanyLive.FormComponent do
end
end

defp save_company(socket, :new, company_params) do
case Companies.create_company(company_params) do
{:ok, company} ->
case consume_image_data(company, socket) do
{:ok, _company} ->
{:noreply,
socket
|> put_flash(:info, "Company created successfully")
|> push_patch(to: socket.assigns.patch)}
end

{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, form: to_form(changeset))}
end
end

defp consume_image_data(company, socket) do
consume_uploaded_entries(socket, :logo, fn %{path: path}, entry ->
Companies.update_company_logo(company, %{
Expand Down
Loading