Skip to content
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: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ gallium-*.tar
npm-debug.log
/assets/node_modules/
.env.dev
.env
11 changes: 11 additions & 0 deletions lib/gallium/ticketing.ex
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,17 @@ defmodule Gallium.Ticketing do
Repo.get_by(Attendee, user_id: user_id)
end

def get_purchase_by_user_id(user_id) do
attendee =
Attendee
|> Repo.get_by(user_id: user_id)
|> Repo.preload([:accompany, :payment])

ticket = Repo.get_by(Ticket, user_id: user_id)

%{attendee: attendee, ticket: ticket}
end

def create_booking(form_data, has_accompany?, user_id) do
data =
form_data
Expand Down
4 changes: 4 additions & 0 deletions lib/gallium/ticketing/checkout_form.ex
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ defmodule Gallium.Ticketing.CheckoutForm do
)
|> validate_cesium_member(:student_number, :is_cesium_member)
|> validate_format(:nif, ~r/^\d{9}$/, message: "O NIF tem de ter exatamente 9 números")
|> validate_length(:table_preference,
max: 30,
message: "A preferência da mesa tem de ter no máximo 30 caracteres"
)
|> cast_embed(:accompany, with: &accompany_changeset/2)
end

Expand Down
4 changes: 2 additions & 2 deletions lib/gallium_web/components/forecast.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ defmodule GalliumWeb.Components.Forecast do
/>
<.card
icon="hero-musical-note"
title="Karaoke & Animação"
description="Diverte-te com karaoke, cantores quebra-gelo e muitas outras surpresas ao longo da noite."
title="Animação"
description="Diverte-te com cartões quebra-gelo e muitas outras surpresas ao longo da noite."
/>
<.card
icon="hero-camera"
Expand Down
12 changes: 2 additions & 10 deletions lib/gallium_web/components/info_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,15 @@ defmodule GalliumWeb.Components.InfoSection do
<p>Viagem de Ida</p>
</div>
<p class="text-xs font-glacial opacity-70 uppercase mt-1">Paragem da UM • Quinta</p>
<p class="text-3xl md:text-4xl mt-2 font-amarante">18H30 • 19H</p>
</div>

<div class="text-center mb-8 flex flex-col items-center">
<div class="flex items-center space-x-2 font-glacial text-xs tracking-[0.3em] uppercase opacity-80">
<.icon name="hero-clock" class="h-5 w-5" />
<p>Sessão Fotográfica</p>
</div>
<p class="text-3xl md:text-4xl mt-2 font-amarante">19H30 • 20H</p>
<p class="text-3xl md:text-4xl mt-2 font-amarante">18H • 19:00H</p>
</div>

<div class="text-center flex flex-col items-center">
<div class="flex items-center space-x-2 font-glacial text-xs tracking-[0.3em] uppercase opacity-80">
<.icon name="hero-truck" class="h-5 w-5" />
<p>Regresso</p>
</div>
<p class="text-3xl md:text-4xl mt-2 font-amarante">3H</p>
<p class="text-3xl md:text-4xl mt-2 font-amarante">2H</p>
</div>
</div>

Expand Down
16 changes: 8 additions & 8 deletions lib/gallium_web/components/program_section.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ defmodule GalliumWeb.Components.ProgramSection do
attr :schedule_items, :list,
default: [
%{
time: "18H30",
time: "18H",
title: "PARTIDA DA UNIVERSIDADE",
description: "Encontro na paragem da UM para embarque no autocarro"
},
%{
time: "19H30",
title: "CHEGADA À QUINTA DA ALDEIA",
time: "19H",
title: "CHEGADA À QUINTA VINHA DO CABO",
description: "Recepção com entradas e bebidas de boas-vindas"
},
%{
Expand All @@ -26,15 +26,15 @@ defmodule GalliumWeb.Components.ProgramSection do
%{
time: "20H30",
title: "JANTAR",
description: "Sopa, prato principal e sobremesa servidos à mesa"
description: "Sopa, prato principal e sobremesa"
},
%{
time: "22H00",
title: "ANIMAÇÃO & KARAOKE",
description: "Música, karaoke, cartões quebra-gelo e muitas surpresas"
title: "ANIMAÇÃO",
description: "Música, cartões quebra-gelo e muitas surpresas"
},
%{
time: "3H",
time: "2H",
title: "REGRESSO À UNIVERSIDADE",
description: "Autocarro de volta ao ponto de partida"
}
Expand Down Expand Up @@ -67,7 +67,7 @@ defmodule GalliumWeb.Components.ProgramSection do
</div>

<p class="mt-16 text-bronze text-sm text-center tracking-[2px] max-w-md">
Não percas atividades incríveis como karaoke, cartões quebra-gelo e muitas outras surpresas!
Não percas atividades incríveis como cartões quebra-gelo e muitas outras surpresas!
</p>
</div>
</section>
Expand Down
4 changes: 2 additions & 2 deletions lib/gallium_web/components/ticket_section.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ defmodule GalliumWeb.Components.TicketSection do
"Jantar completo",
"Sessão fotográfica",
"Transporte opcional",
"Animação e karaoke"
"Animação"
]}
/>

Expand All @@ -47,7 +47,7 @@ defmodule GalliumWeb.Components.TicketSection do
"Jantar completo",
"Sessão fotográfica",
"Transporte opcional",
"Animação e karaoke"
"Animação"
]}
/>
</div>
Expand Down
4 changes: 2 additions & 2 deletions lib/gallium_web/controllers/midas_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ defmodule GalliumWeb.MidasController do
alias Gallium.Ticketing

def handle_webhook(conn, %{"api_key" => api_key, "order_id" => order_id}) do
if api_key == Application.fetch_env!(:gallium, :midas)[:midas_api_key] do
if api_key == Application.fetch_env!(:gallium, :midas)[:api_key] do
process_payment_if_pending(conn, order_id)
else
send_resp(conn, 403, "invalid api key")
end
end

def payment_received(conn, %{"orderId" => order_id, "key" => api_key}) do
if api_key == Application.fetch_env!(:gallium, :midas)[:midas_api_key] do
if api_key == Application.fetch_env!(:gallium, :midas)[:api_key] do
process_payment_if_pending(conn, order_id)
else
send_resp(conn, 403, "invalid api key")
Expand Down
2 changes: 1 addition & 1 deletion lib/gallium_web/live/landing/index.html.heex
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div class="">
<.hero
date_info="05/06 • Quinta da Aldeia"
date_info="05/06 • Quinta Vinha do Cabo"
ticket_url="/tickets/buy"
/>

Expand Down
155 changes: 127 additions & 28 deletions lib/gallium_web/live/ticketing_purchase/index.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ defmodule GalliumWeb.TicketingPurchaseLive.Index do
use GalliumWeb, :app_view
import GalliumWeb.Components.{Button, Stepper}

alias Gallium.Accounts
alias Gallium.Ticketing
alias Gallium.Ticketing.{CheckoutForm, TicketType}

Expand All @@ -13,44 +12,34 @@ defmodule GalliumWeb.TicketingPurchaseLive.Index do
def mount(params, _session, socket) do
# takes the type of the ticket from the url
is_cesium_member? = Map.get(params, "type", "nao_socio") == "socio"
user_id = socket.assigns.current_scope.user.id
purchase = Ticketing.get_purchase_by_user_id(user_id)

user_info =
case Accounts.get_user_info_by_id(socket.assigns.current_scope.user.id) do
{:ok, %{payment: %{status: status}} = attendee} when status in [:paid, :pending] ->
attendee
socket =
case purchase_state(purchase) do
{:blocked, attendee} ->
assign_blocked_purchase(socket, attendee, is_cesium_member?)

_ ->
nil
end
{:resume, attendee} ->
assign_resumable_purchase(socket, attendee)

initial_changeset =
CheckoutForm.changeset_personal_data(
%CheckoutForm{is_cesium_member: is_cesium_member?},
%{}
)

price_per_ticket =
if is_cesium_member?,
do: TicketType.price_for_member(),
else: TicketType.price_for_non_member()
:new ->
assign_new_purchase(socket, is_cesium_member?)
end

{:ok,
socket
|> assign(:current_step, 1)
|> assign(:form_data, to_form(initial_changeset))
|> assign(:has_accompany?, false)
|> assign(:amount_to_pay, nil)
|> assign(:price_per_ticket, price_per_ticket)
|> assign(:companion_price, price_per_ticket)
|> assign(:payment_status, :pending)
|> assign(:user_info, user_info)}
{:ok, socket}
end

@impl true
def handle_event("next_step", _params, socket) do
{:noreply, update(socket, :current_step, &(&1 + 1))}
end

@impl true
def handle_event("previous_step", _params, %{assigns: %{resuming_purchase?: true}} = socket) do
{:noreply, socket}
end

@impl true
def handle_event("previous_step", _params, socket) do
{:noreply, update(socket, :current_step, &max(&1 - 1, 1))}
Expand Down Expand Up @@ -208,4 +197,114 @@ defmodule GalliumWeb.TicketingPurchaseLive.Index do
def handle_info({:payment_order_updated, payment}, socket) do
{:noreply, assign(socket, :payment_status, payment.status)}
end

defp purchase_state(%{attendee: %{payment: %{status: :paid}} = attendee}) do
{:blocked, attendee}
end

defp purchase_state(%{attendee: attendee}) when not is_nil(attendee) do
{:resume, attendee}
end

defp purchase_state(_purchase), do: :new

defp assign_new_purchase(socket, is_cesium_member?) do
initial_changeset =
CheckoutForm.changeset_personal_data(
%CheckoutForm{is_cesium_member: is_cesium_member?},
%{}
)

price_per_ticket = price_per_ticket(is_cesium_member?)

socket
|> assign(:current_step, 1)
|> assign(:form_data, to_form(initial_changeset))
|> assign(:has_accompany?, false)
|> assign(:amount_to_pay, nil)
|> assign(:price_per_ticket, price_per_ticket)
|> assign(:companion_price, price_per_ticket)
|> assign(:payment_status, :pending)
|> assign(:purchase_blocked?, false)
|> assign(:resuming_purchase?, false)
|> assign(:user_info, nil)
end

defp assign_resumable_purchase(socket, attendee) do
if connected?(socket) && attendee.payment && attendee.payment.status == :pending do
Ticketing.subscribe_to_payment_order_updates(attendee.payment.order_id)
end

has_accompany? = not is_nil(attendee.accompany)
price_per_ticket = price_per_ticket(attendee.is_cesium_member)
amount_to_pay = existing_amount_to_pay(attendee, price_per_ticket, has_accompany?)
checkout_form = checkout_form_from_attendee(attendee)
payment_changeset = CheckoutForm.changeset_payment(checkout_form, %{})

socket
|> assign(:current_step, 3)
|> assign(:form_data, to_form(payment_changeset))
|> assign(:has_accompany?, has_accompany?)
|> assign(:amount_to_pay, amount_to_pay)
|> assign(:price_per_ticket, price_per_ticket)
|> assign(:companion_price, price_per_ticket)
|> assign(:payment_status, payment_status(attendee))
|> assign(:purchase_blocked?, false)
|> assign(:resuming_purchase?, true)
|> assign(:user_info, attendee)
end

defp assign_blocked_purchase(socket, attendee, is_cesium_member?) do
price_per_ticket = price_per_ticket(is_cesium_member?)

socket
|> assign(:current_step, 1)
|> assign(:form_data, to_form(CheckoutForm.changeset_personal_data(%CheckoutForm{}, %{})))
|> assign(:has_accompany?, false)
|> assign(:amount_to_pay, attendee.payment.amount)
|> assign(:price_per_ticket, price_per_ticket)
|> assign(:companion_price, price_per_ticket)
|> assign(:payment_status, :paid)
|> assign(:purchase_blocked?, true)
|> assign(:resuming_purchase?, false)
|> assign(:user_info, attendee)
end

defp checkout_form_from_attendee(attendee) do
%CheckoutForm{
full_name: attendee.full_name,
student_number: attendee.student_number,
phone_number: attendee.phone_number,
nif: attendee.nif,
is_cesium_member: attendee.is_cesium_member,
wants_transport: attendee.wants_transport,
table_preference: attendee.table_preference,
allergies: attendee.allergies,
accompany: accompany_form_from_attendee(attendee.accompany)
}
end

defp accompany_form_from_attendee(nil), do: nil

defp accompany_form_from_attendee(accompany) do
%CheckoutForm.AccompanyForm{
full_name: accompany.full_name,
email: accompany.email,
phone_number: accompany.phone_number
}
end

defp existing_amount_to_pay(%{payment: %{amount: amount}}, _price_per_ticket, _has_accompany?)
when not is_nil(amount) do
amount
end

defp existing_amount_to_pay(_attendee, price_per_ticket, true), do: price_per_ticket * 2
defp existing_amount_to_pay(_attendee, price_per_ticket, false), do: price_per_ticket

defp payment_status(%{payment: %{status: status}}), do: status
defp payment_status(_attendee), do: :pending

defp price_per_ticket(true), do: TicketType.price_for_member()
defp price_per_ticket(false), do: TicketType.price_for_non_member()
end
3 changes: 2 additions & 1 deletion lib/gallium_web/live/ticketing_purchase/index.html.heex
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="grow flex flex-col">
<%= if !@user_info do %>
<%= if !@purchase_blocked? do %>
<div class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 grow w-full py-8">
<%= if @current_step < 4 do %>
<.stepper
Expand All @@ -24,6 +24,7 @@
has_accompany?={@has_accompany?}
amount_to_pay={@amount_to_pay}
price_per_ticket={@price_per_ticket}
resuming_purchase?={@resuming_purchase?}
/>
<% 4 -> %>
<.confirmation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

<p class="font-cormorant text-gray-600 text-center mb-8">
{if @payment_status == :paid,
do: "O teu pagamento foi recebido. Até já na Quinta da Aldeia!",
do: "O teu pagamento foi recebido. Até já na Quinta Vinha do Cabo!",
else:
"A tua reserva foi registada. Efetua o pagamento via MBWay para confirmares o teu bilhete."}
</p>
Expand Down Expand Up @@ -63,7 +63,7 @@
<span>5 de Junho, 2026</span>
</div>
<div class="flex items-center gap-2 font-cormorant">
<span>Quinta da Aldeia, Guimarães</span>
<span>Quinta Vinha do Cabo, Guimarães</span>
</div>
</div>

Expand Down
Loading
Loading