From e2d601f18e2c853f737cf990748905a392fba6a5 Mon Sep 17 00:00:00 2001 From: zafer Date: Sun, 9 Mar 2025 19:10:00 +0200 Subject: [PATCH 1/6] in midst of implementing dev onboarding --- lib/algora_web/live/onboarding/dev.ex | 332 +++++++++++++++----------- 1 file changed, 188 insertions(+), 144 deletions(-) diff --git a/lib/algora_web/live/onboarding/dev.ex b/lib/algora_web/live/onboarding/dev.ex index f345be9af..78bf643b9 100644 --- a/lib/algora_web/live/onboarding/dev.ex +++ b/lib/algora_web/live/onboarding/dev.ex @@ -1,13 +1,39 @@ defmodule AlgoraWeb.Onboarding.DevLive do @moduledoc false use AlgoraWeb, :live_view + use LiveSvelte.Components + import Ecto.Changeset import Ecto.Query alias Algora.Payments.Transaction alias Algora.Repo alias AlgoraWeb.Components.Logos + @steps [:info, :oauth] + + defmodule TechStackForm do + @moduledoc false + use Ecto.Schema + + import Ecto.Changeset + + @primary_key false + embedded_schema do + field :tech_stack, {:array, :string} + end + + def init do + to_form(TechStackForm.changeset(%TechStackForm{}, %{tech_stack: []})) + end + + def changeset(form, attrs) do + form + |> cast(attrs, [:tech_stack]) + |> validate_length(:tech_stack, min: 1, message: "Please enter at least one technology") + end + end + def mount(_params, _session, socket) do context = %{ country: socket.assigns.current_country, @@ -37,10 +63,12 @@ defmodule AlgoraWeb.Onboarding.DevLive do {:ok, socket - |> assign(:step, 1) - |> assign(:total_steps, 2) + |> assign(:step, Enum.at(@steps, 0)) + |> assign(:steps, @steps) + |> assign(:total_steps, length(@steps)) |> assign(:context, context) - |> assign(:transactions, transactions)} + |> assign(:transactions, transactions) + |> assign(:tech_stack_form, TechStackForm.init())} end def render(assigns) do @@ -49,167 +77,147 @@ defmodule AlgoraWeb.Onboarding.DevLive do
-
+
- {@step} / {@total_steps} + {Enum.find_index(@steps, &(&1 == @step)) + 1} / {length(@steps)} -

Get started

+

+ <%= if @step == Enum.at(@steps, -1) do %> + Last step + <% else %> + Get started + <% end %> +

- {render_step(assigns)} -
- -
- <%= if @step > 1 do %> - <.button phx-click="prev_step" variant="secondary"> - Previous - - <% else %> -
- <% end %> - <%= if @step < @total_steps do %> - <.button phx-click="next_step" variant="default"> - Next - - <% else %> - <.button - href={Algora.Github.authorize_url()} - rel="noopener" - class="inline-flex items-center" - > - Sign in with GitHub - - <% end %> + {main_content(assigns)}
-

- Recently Completed Bounties -

- <%= if @transactions == [] do %> -

No completed bounties available

- <% else %> - <%= for transaction <- @transactions do %> -
-
-
-
- {Money.to_string!(transaction.bounty.amount)} -
-
- {transaction.bounty.ticket.repository.user.provider_login}/{transaction.bounty.ticket.repository.name}#{transaction.bounty.ticket.number} -
-
- {transaction.bounty.ticket.title} -
-
- {Algora.Util.time_ago(transaction.succeeded_at)} -
-
+ {sidebar_content(assigns)} +
+
+
+ """ + end -
-

- Awarded to -

- {transaction.user.name} -
- {transaction.user.name} -
- {Algora.Misc.CountryEmojis.get(transaction.user.country, "🌎")} -
-
-
+ defp sidebar_content(assigns) do + ~H""" +

+ Recently Completed Bounties +

+ <%= if @transactions == [] do %> +

No completed bounties available

+ <% else %> + <%= for transaction <- @transactions do %> +
+
+
+
+ {Money.to_string!(transaction.bounty.amount)} +
+
+ {transaction.bounty.ticket.repository.user.provider_login}/{transaction.bounty.ticket.repository.name}#{transaction.bounty.ticket.number} +
+
+ {transaction.bounty.ticket.title} +
+
+ {Algora.Util.time_ago(transaction.succeeded_at)} +
+
+ +
+

+ Awarded to +

+ {transaction.user.name} +
+ {transaction.user.name} +
+ {Algora.Misc.CountryEmojis.get(transaction.user.country, "🌎")}
- <% end %> - <% end %> +
+
-
-
+ <% end %> + <% end %> """ end - def render_step(%{step: 1} = assigns) do + def main_content(%{step: :info} = assigns) do ~H"""
-
-

- What is your tech stack? -

-

Select the technologies you work with

- -
- <.input - type="text" - name="tech_input" - value="" - placeholder="Elixir, Phoenix, PostgreSQL, etc." - phx-keydown="handle_tech_input" - phx-debounce="200" - class="w-full border-input bg-background" + <.form for={@tech_stack_form} phx-submit="submit_tech_stack" class="space-y-8"> +
+

+ What is your tech stack? +

+

Select the technologies you work with

+ + <.TechStack + class="mt-4" + tech={get_field(@tech_stack_form.source, :tech_stack) || []} + socket={@socket} /> -
-
- <%= for tech <- @context.tech_stack do %> -
- {tech} - -
- <% end %> + <.error :for={msg <- @tech_stack_form[:tech_stack].errors |> Enum.map(&translate_error(&1))}> + {msg} +
-
-
-

- What are you looking to do? -

-

Select all that apply

- -
- <%= for {intention, label, description, icon} <- [ - {"bounties", "Solve Bounties", "Work on open source issues and earn rewards", "tabler-diamond"}, - {"jobs", "Find Full-time Work", "Get matched with companies hiring developers", "tabler-briefcase"}, - {"projects", "Freelance Work", "Take on flexible contract-based projects", "tabler-clock"} - ] do %> -
""" end - def render_step(%{step: 2} = assigns) do + def main_content(%{step: :oauth} = assigns) do ~H"""
@@ -226,6 +234,14 @@ defmodule AlgoraWeb.Onboarding.DevLive do and <.link href="/privacy" class="text-primary hover:underline">Privacy Policy.

+
+ <.button phx-click="prev_step" variant="secondary"> + Previous + + <.button href={Algora.Github.authorize_url()} rel="noopener" class="inline-flex items-center"> + Sign in with GitHub + +
""" end @@ -251,12 +267,10 @@ defmodule AlgoraWeb.Onboarding.DevLive do Map.put(context, String.to_atom(field), value) end - def handle_event("next_step", _, socket) do - {:noreply, assign(socket, step: socket.assigns.step + 1)} - end - def handle_event("prev_step", _, socket) do - {:noreply, assign(socket, step: socket.assigns.step - 1)} + current_step_index = Enum.find_index(socket.assigns.steps, &(&1 == socket.assigns.step)) + prev_step = Enum.at(socket.assigns.steps, current_step_index - 1) + {:noreply, assign(socket, :step, prev_step)} end def handle_event("submit", _, socket) do @@ -293,13 +307,43 @@ defmodule AlgoraWeb.Onboarding.DevLive do {:noreply, assign(socket, context: updated_context)} end + def handle_event("tech_stack_changed", %{"tech_stack" => tech_stack}, socket) do + changeset = TechStackForm.changeset(%TechStackForm{}, %{tech_stack: tech_stack}) + {:noreply, assign(socket, :tech_stack_form, to_form(changeset))} + end + def handle_event("handle_tech_input", %{"key" => "Enter", "value" => tech}, socket) when byte_size(tech) > 0 do - updated_tech_stack = Enum.uniq([String.trim(tech) | socket.assigns.context.tech_stack]) - updated_context = Map.put(socket.assigns.context, :tech_stack, updated_tech_stack) - {:noreply, assign(socket, context: updated_context)} + tech_stack = [String.trim(tech) | get_field(socket.assigns.tech_stack_form.source, :tech_stack) || []] + changeset = TechStackForm.changeset(%TechStackForm{}, %{tech_stack: Enum.uniq(tech_stack)}) + {:noreply, assign(socket, :tech_stack_form, to_form(changeset))} end def handle_event("handle_tech_input", _params, socket) do {:noreply, socket} end + + def handle_event("submit_tech_stack", %{"tech_stack_form" => params}, socket) do + tech_stack = + Jason.decode!(params["tech_stack"]) ++ + case String.trim(params["tech_stack_input"]) do + "" -> [] + tech_stack_input -> String.split(tech_stack_input, ",") + end + + changeset = + %TechStackForm{} + |> TechStackForm.changeset(%{tech_stack: tech_stack}) + |> Map.put(:action, :validate) + + case changeset do + %{valid?: true} -> + {:noreply, + socket + |> assign(:tech_stack_form, to_form(changeset)) + |> assign(step: :oauth)} + + %{valid?: false} -> + {:noreply, assign(socket, tech_stack_form: to_form(changeset))} + end + end end From 06fff1f9a7cc2036fba65fabf33a1b66115b92e5 Mon Sep 17 00:00:00 2001 From: zafer Date: Sun, 9 Mar 2025 19:31:18 +0200 Subject: [PATCH 2/6] feat: add intentions selection to dev onboarding info step --- lib/algora_web/live/onboarding/dev.ex | 123 ++++++++------------------ 1 file changed, 36 insertions(+), 87 deletions(-) diff --git a/lib/algora_web/live/onboarding/dev.ex b/lib/algora_web/live/onboarding/dev.ex index 78bf643b9..758b24001 100644 --- a/lib/algora_web/live/onboarding/dev.ex +++ b/lib/algora_web/live/onboarding/dev.ex @@ -12,7 +12,7 @@ defmodule AlgoraWeb.Onboarding.DevLive do @steps [:info, :oauth] - defmodule TechStackForm do + defmodule InfoForm do @moduledoc false use Ecto.Schema @@ -21,16 +21,27 @@ defmodule AlgoraWeb.Onboarding.DevLive do @primary_key false embedded_schema do field :tech_stack, {:array, :string} + field :intentions, {:array, :string} end def init do - to_form(TechStackForm.changeset(%TechStackForm{}, %{tech_stack: []})) + to_form(InfoForm.changeset(%InfoForm{}, %{tech_stack: [], intentions: []})) end def changeset(form, attrs) do form - |> cast(attrs, [:tech_stack]) + |> cast(attrs, [:tech_stack, :intentions]) |> validate_length(:tech_stack, min: 1, message: "Please enter at least one technology") + |> validate_length(:intentions, min: 1, message: "Please select at least one intention") + |> validate_subset(:intentions, Enum.map(intentions_options(), &elem(&1, 0))) + end + + def intentions_options do + [ + {"bounties", "Solve Bounties", "Work on open source issues and earn rewards", "tabler-diamond"}, + {"jobs", "Find Full-time Work", "Get matched with companies hiring developers", "tabler-briefcase"}, + {"projects", "Freelance Work", "Take on flexible contract-based projects", "tabler-clock"} + ] end end @@ -68,7 +79,7 @@ defmodule AlgoraWeb.Onboarding.DevLive do |> assign(:total_steps, length(@steps)) |> assign(:context, context) |> assign(:transactions, transactions) - |> assign(:tech_stack_form, TechStackForm.init())} + |> assign(:info_form, InfoForm.init())} end def render(assigns) do @@ -155,7 +166,7 @@ defmodule AlgoraWeb.Onboarding.DevLive do def main_content(%{step: :info} = assigns) do ~H"""
- <.form for={@tech_stack_form} phx-submit="submit_tech_stack" class="space-y-8"> + <.form for={@info_form} phx-submit="submit_info" class="space-y-8">

What is your tech stack? @@ -164,11 +175,12 @@ defmodule AlgoraWeb.Onboarding.DevLive do <.TechStack class="mt-4" - tech={get_field(@tech_stack_form.source, :tech_stack) || []} + tech={get_field(@info_form.source, :tech_stack) || []} socket={@socket} + form="info_form" /> - <.error :for={msg <- @tech_stack_form[:tech_stack].errors |> Enum.map(&translate_error(&1))}> + <.error :for={msg <- @info_form[:tech_stack].errors |> Enum.map(&translate_error(&1))}> {msg}

@@ -180,18 +192,15 @@ defmodule AlgoraWeb.Onboarding.DevLive do

Select all that apply

- <%= for {intention, label, description, icon} <- [ - {"bounties", "Solve Bounties", "Work on open source issues and earn rewards", "tabler-diamond"}, - {"jobs", "Find Full-time Work", "Get matched with companies hiring developers", "tabler-briefcase"}, - {"projects", "Freelance Work", "Take on flexible contract-based projects", "tabler-clock"} - ] do %> + <%= for {value, label, description, icon} <- InfoForm.intentions_options() do %>