Skip to content

Commit 84b0e82

Browse files
committed
feat: enhance user dashboard with availability form and toggle functionality
- Introduced a new `AvailabilityForm` schema to manage user availability settings, including `hourly_rate_min` and `hours_per_week`. - Updated the user dashboard to include a form for users to specify their availability details. - Implemented event handling for toggling availability and validating/saving the availability form, improving user interaction and data management.
1 parent cddbda5 commit 84b0e82

File tree

3 files changed

+126
-47
lines changed

3 files changed

+126
-47
lines changed

lib/algora/accounts/schemas/user.ex

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,12 @@ defmodule Algora.Accounts.User do
264264
:country,
265265
:location,
266266
:timezone,
267-
:tech_stack
267+
:tech_stack,
268+
:seeking_contracts,
269+
:seeking_bounties,
270+
:seeking_jobs,
271+
:hourly_rate_min,
272+
:hours_per_week
268273
])
269274
|> validate_required([:handle, :display_name])
270275
|> validate_handle()

lib/algora_web/components/ui/switch.ex

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ defmodule AlgoraWeb.Components.UI.Switch do
1111
attr :id, :string, required: true
1212
attr :name, :string, default: nil
1313
attr :value, :boolean, default: nil
14-
14+
attr :on_click, JS, default: %JS{}
1515
attr :field, Phoenix.HTML.FormField, doc: "a form field struct retrieved from the form, for example: @form[:email]"
1616

1717
attr :"default-value", :any, values: [true, false, "true", "false"], default: false
@@ -31,7 +31,7 @@ defmodule AlgoraWeb.Components.UI.Switch do
3131
type="button"
3232
role="switch"
3333
data-state={(@checked && "checked") || "unchecked"}
34-
phx-click={toggle(@id)}
34+
phx-click={@on_click |> toggle(@id)}
3535
class={
3636
classes([
3737
"group/switch inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors data-[state=checked]:bg-success data-[state=unchecked]:bg-input focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50"
@@ -48,8 +48,8 @@ defmodule AlgoraWeb.Components.UI.Switch do
4848
"""
4949
end
5050

51-
defp toggle(id) do
52-
%JS{}
51+
defp toggle(js, id) do
52+
js
5353
|> JS.toggle_attribute({"data-state", "checked", "unchecked"})
5454
|> JS.toggle_attribute({"checked", true}, to: "##{id} input[type=checkbox]")
5555
end

lib/algora_web/live/user/dashboard_live.ex

Lines changed: 116 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,27 @@ defmodule AlgoraWeb.User.DashboardLive do
3030
end
3131
end
3232

33+
defmodule AvailabilityForm do
34+
@moduledoc false
35+
use Ecto.Schema
36+
37+
import Ecto.Changeset
38+
39+
@primary_key false
40+
embedded_schema do
41+
field :hourly_rate_min, :integer
42+
field :hours_per_week, :integer
43+
end
44+
45+
def changeset(form, attrs) do
46+
form
47+
|> cast(attrs, [:hourly_rate_min, :hours_per_week])
48+
|> validate_required([:hourly_rate_min, :hours_per_week])
49+
|> validate_number(:hourly_rate_min, greater_than: 0)
50+
|> validate_number(:hours_per_week, greater_than: 0, less_than_or_equal_to: 40)
51+
end
52+
end
53+
3354
@impl true
3455
def mount(_params, _session, socket) do
3556
if connected?(socket) do
@@ -49,6 +70,14 @@ defmodule AlgoraWeb.User.DashboardLive do
4970
|> SettingsForm.changeset(%{tech_stack: socket.assigns.current_user.tech_stack})
5071
|> to_form()
5172

73+
availability_form =
74+
%AvailabilityForm{}
75+
|> AvailabilityForm.changeset(%{
76+
hourly_rate_min: socket.assigns.current_user.hourly_rate_min,
77+
hours_per_week: socket.assigns.current_user.hours_per_week
78+
})
79+
|> to_form()
80+
5281
total_earned =
5382
case Accounts.fetch_developer_by(handle: socket.assigns.current_user.handle) do
5483
{:ok, user} -> user.total_earned
@@ -58,12 +87,10 @@ defmodule AlgoraWeb.User.DashboardLive do
5887
socket =
5988
socket
6089
|> assign(:view_mode, "compact")
61-
|> assign(:available_to_work, true)
62-
|> assign(:hourly_rate, Money.new!(50, :USD))
63-
|> assign(:hours_per_week, 40)
6490
|> assign(:contracts, contracts)
6591
|> assign(:has_active_account, has_active_account)
6692
|> assign(:settings_form, settings_form)
93+
|> assign(:availability_form, availability_form)
6794
|> assign(:total_earned, total_earned)
6895
|> assign_bounties()
6996
|> assign_achievements()
@@ -152,7 +179,7 @@ defmodule AlgoraWeb.User.DashboardLive do
152179
<!-- Sidebar -->
153180
<aside class="lg:fixed lg:top-16 lg:right-0 lg:bottom-0 lg:w-96 lg:overflow-y-auto scrollbar-thin lg:border-l lg:border-border lg:bg-background p-4 pt-6 sm:p-6 md:p-8">
154181
<!-- Availability Section -->
155-
<%!-- <div class="flex items-center justify-between">
182+
<div class="flex items-center justify-between">
156183
<div class="flex items-center gap-2">
157184
<label for="available" class="text-sm font-medium">Available to work</label>
158185
<.tooltip>
@@ -165,47 +192,46 @@ defmodule AlgoraWeb.User.DashboardLive do
165192
<.switch
166193
id="available"
167194
name="available"
168-
value={@available_to_work}
169-
phx-click="toggle_availability"
195+
value={@current_user.seeking_contracts}
196+
on_click={
197+
%JS{}
198+
|> JS.push("toggle_availability")
199+
|> JS.toggle(to: "#availability-details")
200+
}
170201
/>
171202
</div>
172-
<div class="mt-4 grid grid-cols-2 gap-4">
173-
<div>
174-
<label for="hourly-rate" class="text-sm font-medium">Hourly rate (USD)</label>
175-
<div class="relative mt-2">
176-
<span class="font-display absolute top-1/2 left-3 -translate-y-1/2">
177-
$
178-
</span>
179-
<.input
180-
type="number"
181-
min="0"
182-
id="hourly-rate"
183-
name="hourly-rate"
184-
value={@hourly_rate}
185-
phx-keydown="handle_hourly_rate"
186-
phx-debounce="200"
187-
phx-hook="ClearInput"
188-
class="font-display w-full border-input bg-background ps-6"
189-
/>
190-
</div>
191-
</div>
192-
<div>
193-
<label for="hours-per-week" class="text-sm font-medium">Hours per week</label>
194-
<.input
195-
type="number"
196-
min="0"
197-
max="168"
198-
id="hours-per-week"
199-
name="hours-per-week"
200-
value={@hours_per_week}
201-
phx-keydown="handle_hours_per_week"
202-
phx-debounce="200"
203-
class="font-display mt-2 w-full border-input bg-background"
204-
/>
205-
</div>
206-
</div> --%>
203+
<.form
204+
for={@availability_form}
205+
id="availability-form"
206+
phx-change="validate_availability"
207+
phx-submit="save_availability"
208+
class={
209+
classes([
210+
"mt-4 grid grid-cols-1 lg:grid-cols-2 gap-4",
211+
@current_user.seeking_contracts || "hidden"
212+
])
213+
}
214+
>
215+
<.input
216+
field={@availability_form[:hourly_rate_min]}
217+
label="Hourly Rate (USD)"
218+
icon="tabler-currency-dollar"
219+
/>
220+
<.input
221+
field={@availability_form[:hours_per_week]}
222+
label="Hours per Week"
223+
icon="tabler-clock"
224+
/>
225+
<.button
226+
:if={@availability_form.source.action == :validate}
227+
type="submit"
228+
class="lg:col-span-2"
229+
>
230+
Save
231+
</.button>
232+
</.form>
207233
<!-- Tech Stack Section -->
208-
<div>
234+
<div class="mt-4">
209235
<h2 class="mb-2 text-xl font-semibold">
210236
Tech stack
211237
</h2>
@@ -359,6 +385,54 @@ defmodule AlgoraWeb.User.DashboardLive do
359385
end
360386
end
361387

388+
@impl true
389+
def handle_event("toggle_availability", _params, socket) do
390+
current_user = socket.assigns.current_user
391+
392+
{:ok, user} = Accounts.update_settings(current_user, %{seeking_contracts: !current_user.seeking_contracts})
393+
394+
{:noreply, assign(socket, :current_user, user)}
395+
end
396+
397+
@impl true
398+
def handle_event("validate_availability", %{"availability_form" => params}, socket) do
399+
form =
400+
%AvailabilityForm{}
401+
|> AvailabilityForm.changeset(params)
402+
|> Map.put(:action, :validate)
403+
|> to_form()
404+
405+
{:noreply, assign(socket, availability_form: form)}
406+
end
407+
408+
@impl true
409+
def handle_event("save_availability", %{"availability_form" => params}, socket) do
410+
changeset =
411+
%AvailabilityForm{}
412+
|> AvailabilityForm.changeset(params)
413+
|> Map.put(:action, :validate)
414+
415+
case changeset do
416+
%{valid?: true} ->
417+
case socket.assigns.current_user
418+
|> User.settings_changeset(params)
419+
|> Repo.update() do
420+
{:ok, user} ->
421+
{:noreply,
422+
socket
423+
|> put_flash(:info, "Availability updated!")
424+
|> assign(:current_user, user)
425+
|> assign(:availability_form, changeset |> Map.put(:action, nil) |> to_form())}
426+
427+
{:error, _changeset} ->
428+
{:noreply, put_flash(socket, :error, "Failed to update availability")}
429+
end
430+
431+
%{valid?: false} ->
432+
{:noreply, assign(socket, availability_form: to_form(changeset))}
433+
end
434+
end
435+
362436
@impl true
363437
def handle_info(:bounties_updated, socket) do
364438
{:noreply, socket}

0 commit comments

Comments
 (0)