diff --git a/lib/algora/accounts/accounts.ex b/lib/algora/accounts/accounts.ex index 4a0cce3d8..871c7d667 100644 --- a/lib/algora/accounts/accounts.ex +++ b/lib/algora/accounts/accounts.ex @@ -526,7 +526,7 @@ defmodule Algora.Accounts do ) ) - if membership do + if membership || user.is_admin do update_settings(user, %{last_context: context}) else {:error, :unauthorized} diff --git a/lib/algora/settings/settings.ex b/lib/algora/settings/settings.ex index 5dd3271fe..79f582045 100644 --- a/lib/algora/settings/settings.ex +++ b/lib/algora/settings/settings.ex @@ -50,6 +50,17 @@ defmodule Algora.Settings do set("featured_developers", %{"handles" => handles}) end + def get_featured_collabs do + case get("featured_collabs") do + %{"handles" => handles} when is_list(handles) -> handles + _ -> nil + end + end + + def set_featured_collabs(handles) when is_list(handles) do + set("featured_collabs", %{"handles" => handles}) + end + def get_org_matches(org) do case get("org_matches:#{org.handle}") do %{"matches" => matches} when is_list(matches) -> diff --git a/lib/algora_web/components/autopay.ex b/lib/algora_web/components/autopay.ex new file mode 100644 index 000000000..656d4287b --- /dev/null +++ b/lib/algora_web/components/autopay.ex @@ -0,0 +1,95 @@ +defmodule AlgoraWeb.Components.Autopay do + @moduledoc false + use AlgoraWeb.Component + + def autopay(assigns) do + ~H""" +
+
+
+ +

+ Merged pull request +

+
+
+
+
+ +

+ Charged saved payment method +

+
+
+
+
+ + + + + + +

+ Transferring funds to contributor +

+
+
+
+ """ + end +end diff --git a/lib/algora_web/live/home_live.ex b/lib/algora_web/live/home_live.ex index 1d3b38e8b..cecd4be6e 100644 --- a/lib/algora_web/live/home_live.ex +++ b/lib/algora_web/live/home_live.ex @@ -8,8 +8,6 @@ defmodule AlgoraWeb.HomeLive do alias Algora.Accounts alias Algora.Accounts.User - alias Algora.Bounties - alias Algora.Github alias Algora.Payments.Transaction alias Algora.Repo alias Algora.Workspace @@ -18,6 +16,8 @@ defmodule AlgoraWeb.HomeLive do alias AlgoraWeb.Components.Logos alias AlgoraWeb.Data.PlatformStats alias AlgoraWeb.Forms.BountyForm + alias AlgoraWeb.Forms.ContractForm + alias AlgoraWeb.Forms.RepoForm alias AlgoraWeb.Forms.TipForm require Logger @@ -44,67 +44,97 @@ defmodule AlgoraWeb.HomeLive do def mount(%{"country_code" => country_code} = params, _session, socket) do Gettext.put_locale(AlgoraWeb.Gettext, Algora.Util.locale_from_country_code(country_code)) + total_contributors = get_contributors_count() + total_countries = get_countries_count() + stats = [ %{label: "Paid Out", value: format_money(get_total_paid_out())}, %{label: "Completed Bounties", value: format_number(get_completed_bounties_count())}, - %{label: "Contributors", value: format_number(get_contributors_count())}, - %{label: "Countries", value: format_number(get_countries_count())} + %{label: "Contributors", value: format_number(total_contributors)}, + %{label: "Countries", value: format_number(total_countries)} ] - if connected?(socket) do - Phoenix.PubSub.subscribe(Algora.PubSub, "auth:#{socket.id}") - end + featured_devs = Accounts.list_featured_developers(country_code) + featured_collabs = list_featured_collabs() {:ok, socket |> assign(:page_title, "Algora - The open source Upwork for engineers") |> assign(:page_image, "#{AlgoraWeb.Endpoint.url()}/images/og/home.png") |> assign(:screenshot?, not is_nil(params["screenshot"])) - |> assign(:oauth_url, Github.authorize_url(%{socket_id: socket.id})) - |> assign(:featured_devs, Accounts.list_featured_developers(country_code)) + |> assign(:featured_devs, featured_devs) + |> assign(:featured_collabs, featured_collabs) |> assign(:stats, stats) |> assign(:bounty_form, to_form(BountyForm.changeset(%BountyForm{}, %{}))) + |> assign(:contract_form, to_form(ContractForm.changeset(%ContractForm{}, %{}))) |> assign(:tip_form, to_form(TipForm.changeset(%TipForm{}, %{}))) |> assign(:repo_form, to_form(RepoForm.changeset(%RepoForm{}, %{}))) - |> assign(:pending_action, nil) |> assign(:plans1, AlgoraWeb.PricingLive.get_plans1()) - |> assign(:plans2, AlgoraWeb.PricingLive.get_plans2())} + |> assign(:total_contributors, total_contributors) + |> assign(:total_countries, total_countries) + |> assign(:selected_developer, nil) + |> assign(:share_drawer_type, nil) + |> assign(:show_share_drawer, false)} end - attr :src, :string, required: true - attr :poster, :string, required: true - attr :title, :string, default: nil - attr :alt, :string, default: nil - attr :class, :string, default: nil - attr :autoplay, :boolean, default: true - attr :start, :integer, default: 0 - - defp modal_video(assigns) do - ~H""" -
JS.set_attribute({"src", @src <> "?autoplay=#{@autoplay}&start=#{@start}"}, - to: "#video-modal-iframe" - ) - |> JS.set_attribute({"title", @title}, to: "#video-modal-iframe") - |> show_modal("video-modal") + ] + end + + defp user_features do + [ + %{ + title: "Bounties & contracts", + description: "Work on new projects and grow your career", + src: ~p"/images/screenshots/user-dashboard.png" + }, + %{ + title: "Your new resume", + description: "Showcase your open source contributions", + src: ~p"/images/screenshots/profile.png" + }, + %{ + title: "Embed on your site", + description: "Let anyone share a bounty/contract with you", + src: ~p"/images/screenshots/embed-profile.png" + }, + %{ + title: "Payment history", + description: "Monitor your earnings in real-time", + src: ~p"/images/screenshots/user-transactions.png" } - > - {@alt} -
-
- <.icon name="tabler-player-play-filled" class="size-5 sm:size-8 text-white" /> -
-
-
- """ + ] end @impl true @@ -119,10 +149,7 @@ defmodule AlgoraWeb.HomeLive do
- - + <.pattern />
@@ -219,7 +246,7 @@ defmodule AlgoraWeb.HomeLive do type="submit" class="absolute right-2 top-1.5 sm:top-2 bottom-1.5 sm:bottom-2 px-2 sm:px-8 h-7 sm:h-[3rem] text-sm sm:text-xl sm:font-semibold drop-shadow-[0_1px_5px_#34d39980] rounded-lg sm:rounded-xl" > - Get Started + Let's try this
@@ -227,111 +254,183 @@ defmodule AlgoraWeb.HomeLive do
-
-
-

- Hire by building product -

-

- Use bounties to evaluate developers based on actual contributions to your codebase -

- -
-
-
- <.modal_video - class="aspect-[9/16] rounded-xl lg:rounded-2xl lg:rounded-r-none" - src="https://www.youtube.com/embed/xObOGcUdtY0" - start={122} - title="$15,000 Open source bounty to hire a Rust engineer" - poster={~p"/images/people/john-de-goes.jpg"} - alt="John A De Goes" - /> -
-
- <.link - href="https://github.com/golemcloud/golem/issues/1004" - rel="noopener" - target="_blank" - class="relative flex aspect-[1121/1343] w-full items-center justify-center overflow-hidden rounded-xl lg:rounded-2xl lg:rounded-l-none" +
+

+ Everything you need to + reward your contributors +

+

+ Build your product and team in one place +

+ - -
-
-
- <.modal_video - class="rounded-xl lg:rounded-2xl" - src="https://www.youtube.com/embed/FXQVD02rfg8" - start={8} - title="How Nick got a job with Open Source Software" - poster="https://img.youtube.com/vi/FXQVD02rfg8/maxresdefault.jpg" - alt="Eric Allam" +
+
+ <%= for {feature, index} <- org_features() |> Enum.with_index() do %> + {feature.title} + <% end %> +
+
+
+
+ <%= for feature <- org_features() do %> +
+
+ {feature.title}
-
-

- It was the easiest hire - because we already knew how great he was -

-
-
-
-
- Eric Allam +
+ {feature.description} +
+
+ {feature.title} +
+
+ <% end %> +
+
+ +
+
🌎
+

+ Join {@total_contributors} contributorsfrom {@total_countries} countries +

+ +

+

+ +
+ <.contributors featured_collabs={@featured_collabs} /> +
+
+ +
+

+ Get paid for open source + and freelance work +

+

+ Work on your own schedule, anywhere in the world +

+
- -
-
-
- <.modal_video - class="rounded-xl lg:rounded-2xl" - src="https://www.youtube.com/embed/3wZGDuoPajk" - start={13} - title="OSS Bounties & Hiring engineers on Algora.io | Founder Testimonial" - poster={~p"/images/misc/3wZGDuoPajk.png"} - alt="Tushar Mathur" +
+
+ <%= for {feature, index} <- user_features() |> Enum.with_index() do %> + {feature.title} + <% end %> +
+
+
+
+ <%= for feature <- user_features() do %> +
+
+ {feature.title}
-
-

- Bounties help us control our burn rate, get work done & meet new hires. I've made - 3 full-time hires - using Algora -

-
-
-
-
- Tushar Mathur -
-
- Founder & CEO at Tailcall -
-
-
-
+
+ {feature.description} +
+
+ {feature.title}
-
+ <% end %>
@@ -452,106 +551,10 @@ defmodule AlgoraWeb.HomeLive do
- <.dialog - id="video-modal" - show={false} - class="aspect-video h-full sm:h-auto w-full lg:max-w-none p-2 sm:p-4 lg:p-[5rem]" - on_cancel={ - %JS{} - |> JS.set_attribute({"src", ""}, to: "#video-modal-iframe") - |> JS.set_attribute({"title", ""}, to: "#video-modal-iframe") - } - > - <.dialog_content class="flex items-center justify-center"> - - - + {share_drawer(assigns)} """ end - @impl true - def handle_event("create_bounty" = event, %{"bounty_form" => params} = unsigned_params, socket) do - changeset = - %BountyForm{} - |> BountyForm.changeset(params) - |> Map.put(:action, :validate) - - amount = get_field(changeset, :amount) - ticket_ref = get_field(changeset, :ticket_ref) - - if changeset.valid? do - if user = socket.assigns[:current_user] do - case Bounties.create_bounty(%{creator: user, owner: user, amount: amount, ticket_ref: ticket_ref}) do - {:ok, _bounty} -> - user |> change(last_context: user.handle) |> Repo.update() - - {:noreply, - socket - |> put_flash(:info, "Bounty created") - |> redirect(to: AlgoraWeb.UserAuth.generate_login_path(user.email))} - - {:error, :already_exists} -> - {:noreply, put_flash(socket, :warning, "You already have a bounty for this ticket")} - - {:error, _reason} -> - {:noreply, put_flash(socket, :error, "Something went wrong")} - end - else - {:noreply, - socket - |> assign(:pending_action, {event, unsigned_params}) - |> push_event("open_popup", %{url: socket.assigns.oauth_url})} - end - else - {:noreply, assign(socket, :bounty_form, to_form(changeset))} - end - end - - @impl true - def handle_event("create_tip" = event, %{"tip_form" => params} = unsigned_params, socket) do - changeset = - %TipForm{} - |> TipForm.changeset(params) - |> Map.put(:action, :validate) - - if changeset.valid? do - if user = socket.assigns[:current_user] do - with {:ok, token} <- Accounts.get_access_token(user), - {:ok, recipient} <- - Workspace.ensure_user(token, get_field(changeset, :github_handle)), - {:ok, checkout_url} <- - Bounties.create_tip(%{ - creator: user, - owner: user, - recipient: recipient, - amount: get_field(changeset, :amount) - }) do - user |> change(last_context: user.handle) |> Repo.update() - {:noreply, redirect(socket, to: AlgoraWeb.UserAuth.generate_login_path(user.email, checkout_url))} - else - {:error, reason} -> - Logger.error("Failed to create tip: #{inspect(reason)}") - {:noreply, put_flash(socket, :error, "Something went wrong")} - end - else - {:noreply, - socket - |> assign(:pending_action, {event, unsigned_params}) - |> push_event("open_popup", %{url: socket.assigns.oauth_url})} - end - else - {:noreply, assign(socket, :tip_form, to_form(changeset))} - end - end - @impl true def handle_event("submit_repo", %{"repo_form" => params}, socket) do changeset = @@ -584,16 +587,24 @@ defmodule AlgoraWeb.HomeLive do end @impl true - def handle_info({:authenticated, user}, socket) do - socket = assign(socket, :current_user, user) + def handle_event("close_share_drawer", _params, socket) do + {:noreply, assign(socket, :show_share_drawer, false)} + end - case socket.assigns.pending_action do - {event, params} -> - socket = assign(socket, :pending_action, nil) - handle_event(event, params, socket) + @impl true + def handle_event("share_opportunity", %{"user_id" => user_id, "type" => type}, socket) do + collab = Enum.find(socket.assigns.featured_collabs, &(&1.user.id == user_id)) + case collab do nil -> {:noreply, socket} + + collab -> + {:noreply, + socket + |> assign(:selected_developer, collab.user) + |> assign(:share_drawer_type, type) + |> assign(:show_share_drawer, true)} end end @@ -702,246 +713,6 @@ defmodule AlgoraWeb.HomeLive do defp format_number(number), do: Number.Delimit.number_to_delimited(number, precision: 0) - defp yc_logo_cloud(assigns) do - ~H""" -
-
- <.link - class="font-bold font-display text-base sm:text-4xl whitespace-nowrap flex items-center justify-center" - navigate={~p"/org/browser-use"} - > - Browser Use - - <.link class="relative flex items-center justify-center" navigate={~p"/org/outerbase"}> - - - - - - - - - - - - - - - - - - - - - - <.link class="relative flex items-center justify-center" navigate={~p"/org/triggerdotdev"}> - Trigger.dev - - <.link class="relative flex items-center justify-center" navigate={~p"/org/traceloop"}> - Traceloop - - <.link - class="font-bold font-display text-base sm:text-5xl whitespace-nowrap flex items-center justify-center" - navigate={~p"/org/trieve"} - > - Trieve logo Trieve - - <.link - class="font-bold font-display text-base sm:text-5xl whitespace-nowrap flex items-center justify-center" - navigate={~p"/org/twentyhq"} - > - - - - Twenty - - <.link class="relative flex items-center justify-center" navigate={~p"/org/aidenybai"}> - Million - - <.link class="relative flex items-center justify-center" navigate={~p"/org/moonrepo"}> - moon - - <.link class="relative flex items-center justify-center" navigate={~p"/org/dittofeed"}> - Dittofeed - - - <.link - class="relative flex items-center justify-center brightness-0 invert" - navigate={~p"/org/onyx-dot-app"} - > - Onyx Logo - - - <.link - class="font-bold font-display text-base sm:text-4xl whitespace-nowrap flex items-center justify-center brightness-0 invert" - aria-label="Logo" - navigate={~p"/org/mendableai"} - > - 🔥 - Firecrawl - - - <.link class="relative flex items-center justify-center" navigate={~p"/org/keephq"}> - Keep - - - <.link - class="font-bold font-display text-base sm:text-5xl whitespace-nowrap flex items-center justify-center" - navigate={~p"/org/windmill-labs"} - > - Windmill Windmill - - - <.link class="relative flex items-center justify-center" navigate={~p"/org/panoratech"}> - Panora - - - <.link class="relative flex items-center justify-center" navigate={~p"/org/highlight"}> - Highlight - -
-
- """ - end - - defp pattern(assigns) do - ~H""" - - -
- - - - - - -
- - - """ - end - defp glow(assigns) do ~H""" <%!-- "absolute top-[-320px] md:top-[-480px] xl:right-[120px] -z-[10]" --%> @@ -1371,4 +1142,464 @@ defmodule AlgoraWeb.HomeLive do """ end + + defp list_featured_collabs do + developers = + case Algora.Settings.get_featured_collabs() do + handles when is_list(handles) and handles != [] -> + developers = Accounts.list_developers(handles: handles) + # Sort developers to match handles order + Enum.sort_by(developers, fn dev -> + Enum.find_index(handles, &(&1 == dev.provider_login)) + end) + + _ -> + Accounts.list_developers(limit: 5) + end + + Enum.map(developers, fn user -> %{user: user, projects: Accounts.list_contributed_projects(user, limit: 2)} end) + end + + defp collab_card(assigns) do + ~H""" +
+
+
+ <.link navigate={User.url(@collab.user)}> + <.avatar class="h-20 w-20 rounded-full"> + <.avatar_image src={@collab.user.avatar_url} alt={@collab.user.name} /> + <.avatar_fallback class="rounded-lg"> + {Algora.Util.initials(@collab.user.name)} + + + + +
+
+ <.link + navigate={User.url(@collab.user)} + class="text-lg sm:text-xl font-semibold truncate" + > + {@collab.user.name} {Algora.Misc.CountryEmojis.get(@collab.user.country)} + +
+
+ <.link + :if={@collab.user.provider_login} + href={"https://github.com/#{@collab.user.provider_login}"} + target="_blank" + class="flex items-center gap-1 hover:underline" + > + + {@collab.user.provider_login} + + <.link + :if={@collab.user.provider_meta["twitter_handle"]} + href={"https://x.com/#{@collab.user.provider_meta["twitter_handle"]}"} + target="_blank" + class="flex items-center gap-1 hover:underline" + > + <.icon name="tabler-brand-x" class="shrink-0 h-4 w-4" /> + {@collab.user.provider_meta["twitter_handle"]} + +
+
+ <%= for tech <- @collab.user.tech_stack |> Enum.reject(& &1 in ["HTML", "MDX", "Dockerfile"]) |> Enum.take(3) do %> + <.badge variant="outline">{tech} + <% end %> +
+
+
+
+ + + +
+
+
+ Completed + + {@collab.user.transactions_count} + {ngettext( + "bounty", + "bounties", + @collab.user.transactions_count + )} + + + {ngettext( + "in %{count} project", + "across %{count} projects", + @collab.user.contributed_projects_count + )} + +
+ + +{Money.to_string!(@collab.user.total_earned)} + +
+
+ <%= for {project, total_earned} <- @collab.projects |> Enum.take(2) do %> + <.link + navigate={User.url(project)} + class="flex flex-1 items-center gap-2 sm:gap-4 text-sm rounded-lg" + > + <.avatar class="h-10 w-10 rounded-lg saturate-0"> + <.avatar_image src={project.avatar_url} alt={project.name} /> + <.avatar_fallback class="rounded-lg"> + {Algora.Util.initials(project.name)} + + +
+
+ {project.name} +
+ +
+
+ <.icon name="tabler-star-filled" class="size-4 text-amber-400 mr-1" />{Algora.Util.format_number_compact( + project.stargazers_count + )} +
+
+ + {total_earned} + + awarded +
+
+
+ + <% end %> +
+
+
+ """ + end + + defp contributors(assigns) do + ~H""" +
+ <%= for collab <- @featured_collabs do %> + <.collab_card collab={collab} /> + <% end %> +
+ """ + end + + defp share_drawer_header(%{share_drawer_type: "contract"} = assigns) do + ~H""" + <.drawer_header> + <.drawer_title>Offer Contract + <.drawer_description> + {@selected_developer.name} will be notified and can accept or decline. You can auto-renew or cancel the contract at the end of each period. + + + """ + end + + defp share_drawer_header(%{share_drawer_type: "bounty"} = assigns) do + ~H""" + <.drawer_header> + <.drawer_title>Share Bounty + <.drawer_description> + Share a bounty opportunity with {@selected_developer.name}. They will be notified and can choose to work on it. + + + """ + end + + defp share_drawer_header(%{share_drawer_type: "tip"} = assigns) do + ~H""" + <.drawer_header> + <.drawer_title>Send Tip + <.drawer_description> + Send a tip to {@selected_developer.name} to show appreciation for their contributions. + + + """ + end + + defp share_drawer_content(%{share_drawer_type: "contract"} = assigns) do + ~H""" + <.form for={@contract_form} phx-submit="create_contract"> + <.card> + <.card_header> + <.card_title>Contract Details + + <.card_content class="pt-0 flex flex-col"> +
+ <.input + label="Hourly Rate" + icon="tabler-currency-dollar" + field={@contract_form[:hourly_rate]} + /> + <.input label="Hours per Week" field={@contract_form[:hours_per_week]} /> +
+
+ <.button variant="secondary" phx-click="close_share_drawer" type="button"> + Cancel + + <.button type="submit"> + Send Contract Offer <.icon name="tabler-arrow-right" class="-mr-1 ml-2 h-4 w-4" /> + +
+ + + + """ + end + + defp share_drawer_content(%{share_drawer_type: "bounty"} = assigns) do + ~H""" + <.form for={@bounty_form} phx-submit="create_bounty"> + <.card> + <.card_header> + <.card_title>Bounty Details + + <.card_content class="pt-0 flex flex-col"> +
+ <.input type="hidden" name="bounty_form[visibility]" value="exclusive" /> + <.input + type="hidden" + name="bounty_form[shared_with][]" + value={ + case @selected_developer do + %{handle: nil, provider_id: provider_id} -> [to_string(provider_id)] + %{id: id} -> [id] + end + } + /> + <.input + label="URL" + field={@bounty_form[:url]} + placeholder="https://github.com/owner/repo/issues/123" + /> + <.input label="Amount" icon="tabler-currency-dollar" field={@bounty_form[:amount]} /> +
+
+ <.button variant="secondary" phx-click="close_share_drawer" type="button"> + Cancel + + <.button type="submit"> + Share Bounty <.icon name="tabler-arrow-right" class="-mr-1 ml-2 h-4 w-4" /> + +
+ + + + """ + end + + defp share_drawer_content(%{share_drawer_type: "tip"} = assigns) do + ~H""" + <.form for={@tip_form} phx-submit="create_tip"> + <.card> + <.card_header> + <.card_title>Tip Details + + <.card_content class="pt-0 flex flex-col"> +
+ + <.input label="Amount" icon="tabler-currency-dollar" field={@tip_form[:amount]} /> + <.input + label="URL" + field={@tip_form[:url]} + placeholder="https://github.com/owner/repo/issues/123" + helptext="We'll add a comment to the issue to notify the developer." + /> +
+
+ <.button variant="secondary" phx-click="close_share_drawer" type="button"> + Cancel + + <.button type="submit"> + Send Tip <.icon name="tabler-arrow-right" class="-mr-1 ml-2 h-4 w-4" /> + +
+ + + + """ + end + + defp share_drawer_developer_info(assigns) do + ~H""" + <.card> + <.card_header> + <.card_title>Developer + + <.card_content class="pt-0"> +
+ <.avatar class="h-20 w-20 rounded-full"> + <.avatar_image src={@selected_developer.avatar_url} alt={@selected_developer.name} /> + <.avatar_fallback class="rounded-lg"> + {Algora.Util.initials(@selected_developer.name)} + + + +
+
+ {@selected_developer.name} + {Algora.Misc.CountryEmojis.get(@selected_developer.country)} +
+ +
+ <.link + :if={@selected_developer.provider_login} + href={"https://github.com/#{@selected_developer.provider_login}"} + target="_blank" + class="flex items-center gap-1 hover:underline" + > + + {@selected_developer.provider_login} + + <.link + :if={@selected_developer.provider_meta["twitter_handle"]} + href={"https://x.com/#{@selected_developer.provider_meta["twitter_handle"]}"} + target="_blank" + class="flex items-center gap-1 hover:underline" + > + <.icon name="tabler-brand-x" class="h-4 w-4" /> + + {@selected_developer.provider_meta["twitter_handle"]} + + +
+ <.icon name="tabler-map-pin" class="h-4 w-4" /> + + {@selected_developer.provider_meta["location"]} + +
+
+ <.icon name="tabler-building" class="h-4 w-4" /> + + {@selected_developer.provider_meta["company"] |> String.trim_leading("@")} + +
+
+ +
+ <%= for tech <- @selected_developer.tech_stack do %> +
+ {tech} +
+ <% end %> +
+
+
+ + + """ + end + + defp share_drawer(assigns) do + ~H""" + <.drawer show={@show_share_drawer} direction="bottom" on_cancel="close_share_drawer"> + <.share_drawer_header + :if={@selected_developer} + selected_developer={@selected_developer} + share_drawer_type={@share_drawer_type} + /> + <.drawer_content :if={@selected_developer} class="mt-4"> +
+ <.share_drawer_developer_info selected_developer={@selected_developer} /> +
+
+
+ <.share_drawer_content + :if={@selected_developer} + selected_developer={@selected_developer} + share_drawer_type={@share_drawer_type} + bounty_form={@bounty_form} + tip_form={@tip_form} + contract_form={@contract_form} + /> +
+ <.alert + variant="default" + class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-20 w-auto flex flex-col items-center justify-center gap-2 text-center" + > + <.alert_title>Let's get you started + <.alert_description> + Sign up to create a {@share_drawer_type} with {@selected_developer.name}. + + <.button href={~p"/onboarding/org"} type="button" variant="subtle" class="mt-4"> + Get started + + +
+
+ + + """ + end + + defp pattern(assigns) do + ~H""" + + +
+ + + + + + +
+ + + """ + end end diff --git a/lib/algora_web/live/org/dashboard_live.ex b/lib/algora_web/live/org/dashboard_live.ex index fdb4e861a..aab4c03d5 100644 --- a/lib/algora_web/live/org/dashboard_live.ex +++ b/lib/algora_web/live/org/dashboard_live.ex @@ -1313,7 +1313,7 @@ defmodule AlgoraWeb.Org.DashboardLive do navigate={User.url(@match.user)} class="text-lg sm:text-xl font-semibold hover:underline truncate" > - {@match.user.name} + {@match.user.name} {Algora.Misc.CountryEmojis.get(@match.user.country)} <.badge :if={@match.badge_text} @@ -1812,7 +1812,7 @@ defmodule AlgoraWeb.Org.DashboardLive do <.card_header> <.card_title>Contract Details - <.card_content> + <.card_content class="pt-0 flex flex-col">
<.input label="Hourly Rate" @@ -1821,17 +1821,16 @@ defmodule AlgoraWeb.Org.DashboardLive do /> <.input label="Hours per Week" field={@contract_form[:hours_per_week]} />
+
+ <.button variant="secondary" phx-click="close_share_drawer" type="button"> + Cancel + + <.button type="submit"> + Send Contract Offer <.icon name="tabler-arrow-right" class="-mr-1 ml-2 h-4 w-4" /> + +
- -
- <.button variant="secondary" phx-click="close_share_drawer" type="button"> - Cancel - - <.button type="submit"> - Send Contract Offer <.icon name="tabler-arrow-right" class="-mr-1 ml-2 h-4 w-4" /> - -
""" end @@ -1843,7 +1842,7 @@ defmodule AlgoraWeb.Org.DashboardLive do <.card_header> <.card_title>Bounty Details - <.card_content> + <.card_content class="pt-0 flex flex-col">
<.input type="hidden" name="bounty_form[visibility]" value="exclusive" /> <.input @@ -1863,17 +1862,16 @@ defmodule AlgoraWeb.Org.DashboardLive do /> <.input label="Amount" icon="tabler-currency-dollar" field={@bounty_form[:amount]} />
+
+ <.button variant="secondary" phx-click="close_share_drawer" type="button"> + Cancel + + <.button type="submit"> + Share Bounty <.icon name="tabler-arrow-right" class="-mr-1 ml-2 h-4 w-4" /> + +
- -
- <.button variant="secondary" phx-click="close_share_drawer" type="button"> - Cancel - - <.button type="submit"> - Share Bounty <.icon name="tabler-arrow-right" class="-mr-1 ml-2 h-4 w-4" /> - -
""" end @@ -1885,7 +1883,7 @@ defmodule AlgoraWeb.Org.DashboardLive do <.card_header> <.card_title>Tip Details - <.card_content> + <.card_content class="pt-0 flex flex-col">
+
+ <.button variant="secondary" phx-click="close_share_drawer" type="button"> + Cancel + + <.button type="submit"> + Send Tip <.icon name="tabler-arrow-right" class="-mr-1 ml-2 h-4 w-4" /> + +
- -
- <.button variant="secondary" phx-click="close_share_drawer" type="button"> - Cancel - - <.button type="submit"> - Send Tip <.icon name="tabler-arrow-right" class="-mr-1 ml-2 h-4 w-4" /> - -
""" end @@ -1921,7 +1918,7 @@ defmodule AlgoraWeb.Org.DashboardLive do <.card_header> <.card_title>Developer - <.card_content> + <.card_content class="pt-0">
<.avatar class="h-20 w-20 rounded-full"> <.avatar_image src={@selected_developer.avatar_url} alt={@selected_developer.name} /> @@ -1933,6 +1930,7 @@ defmodule AlgoraWeb.Org.DashboardLive do
{@selected_developer.name} + {Algora.Misc.CountryEmojis.get(@selected_developer.country)}
<.card_header> - <.card_title> -
- Auto-pay on merge +
+
+ <.card_title> + Auto-pay on merge + + <.card_description class="pt-4"> + Once enabled, we will charge your saved payment method automatically when + +
    +
  • a pull request that claims a bounty is merged
  • +
  • + <.badge>/tip + command is used by you or any other + <.link navigate={~p"/org/#{@current_org.handle}/team"} class="font-semibold"> + {@current_org.name} admins + +
  • +
- - <.card_description> - Once enabled, we will charge your saved payment method automatically when - -
    -
  • a pull request that claims a bounty is merged
  • -
  • - <.badge>/tip - command is used by you or any other - <.link navigate={~p"/org/#{@current_org.handle}/team"} class="font-semibold"> - {@current_org.name} admins - -
  • -
+
+ +
+
<.card_content>
diff --git a/lib/algora_web/live/org/transactions_live.ex b/lib/algora_web/live/org/transactions_live.ex index 276dc54c8..ccc1196b8 100644 --- a/lib/algora_web/live/org/transactions_live.ex +++ b/lib/algora_web/live/org/transactions_live.ex @@ -212,7 +212,7 @@ defmodule AlgoraWeb.Org.TransactionsLive do
- <.card> + <.card class="flex justify-between"> <.card_header> <.card_title>Lifetime Volume <.card_description>Total volume of your transactions @@ -221,7 +221,7 @@ defmodule AlgoraWeb.Org.TransactionsLive do {Money.to_string!(@total_volume)} - <.card> + <.card class="flex justify-between"> <.card_header> <.card_title>Total Balance <.card_description>Net balance across all transactions @@ -234,9 +234,6 @@ defmodule AlgoraWeb.Org.TransactionsLive do <.card :if={length(@transactions) > 0}> - <.card_header> - <.card_title>Transaction History - <.card_content>
diff --git a/lib/algora_web/util.ex b/lib/algora_web/util.ex index 8ec27c3f6..eba3d00e9 100644 --- a/lib/algora_web/util.ex +++ b/lib/algora_web/util.ex @@ -4,6 +4,8 @@ defmodule AlgoraWeb.Util do use AlgoraWeb, :verified_routes use AlgoraWeb, :controller + alias Phoenix.LiveView.JS + require Logger def build_safe_redirect(url) do @@ -35,4 +37,11 @@ defmodule AlgoraWeb.Util do def redirect_safe(conn, url) do redirect(conn, AlgoraWeb.Util.build_safe_redirect(url)) end + + def transition(js \\ %JS{}, attr, eq, opts) do + js + |> JS.remove_class(opts[:to], to: "[#{attr}]:not([#{attr}='#{eq}'])") + |> JS.add_class(opts[:from], to: "[#{attr}]:not([#{attr}='#{eq}'])") + |> JS.add_class(opts[:to], to: "[#{attr}='#{eq}']") + end end diff --git a/priv/static/images/screenshots/autopay-on-merge.png b/priv/static/images/screenshots/autopay-on-merge.png new file mode 100644 index 000000000..2af49d9b5 Binary files /dev/null and b/priv/static/images/screenshots/autopay-on-merge.png differ diff --git a/priv/static/images/screenshots/bounty-to-hire-1.png b/priv/static/images/screenshots/bounty-to-hire-1.png new file mode 100644 index 000000000..c1b8b47e7 Binary files /dev/null and b/priv/static/images/screenshots/bounty-to-hire-1.png differ diff --git a/priv/static/images/screenshots/bounty-to-hire-2.png b/priv/static/images/screenshots/bounty-to-hire-2.png new file mode 100644 index 000000000..f636b56f2 Binary files /dev/null and b/priv/static/images/screenshots/bounty-to-hire-2.png differ diff --git a/priv/static/images/screenshots/bounty-to-hire-merged.png b/priv/static/images/screenshots/bounty-to-hire-merged.png new file mode 100644 index 000000000..5ab8a60ca Binary files /dev/null and b/priv/static/images/screenshots/bounty-to-hire-merged.png differ diff --git a/priv/static/images/screenshots/embed-profile.png b/priv/static/images/screenshots/embed-profile.png new file mode 100644 index 000000000..79bb25bf0 Binary files /dev/null and b/priv/static/images/screenshots/embed-profile.png differ diff --git a/priv/static/images/screenshots/global-payments.png b/priv/static/images/screenshots/global-payments.png new file mode 100644 index 000000000..8265b699c Binary files /dev/null and b/priv/static/images/screenshots/global-payments.png differ diff --git a/priv/static/images/screenshots/org-home.png b/priv/static/images/screenshots/org-home.png index 638f5de26..5e18eaa41 100644 Binary files a/priv/static/images/screenshots/org-home.png and b/priv/static/images/screenshots/org-home.png differ diff --git a/priv/static/images/screenshots/org-matches.png b/priv/static/images/screenshots/org-matches.png new file mode 100644 index 000000000..5ae41ba32 Binary files /dev/null and b/priv/static/images/screenshots/org-matches.png differ diff --git a/priv/static/images/screenshots/org-transactions.png b/priv/static/images/screenshots/org-transactions.png new file mode 100644 index 000000000..b3da25f0c Binary files /dev/null and b/priv/static/images/screenshots/org-transactions.png differ diff --git a/priv/static/images/screenshots/pool-bounties.png b/priv/static/images/screenshots/pool-bounties.png new file mode 100644 index 000000000..cccb6f783 Binary files /dev/null and b/priv/static/images/screenshots/pool-bounties.png differ diff --git a/priv/static/images/screenshots/profile.png b/priv/static/images/screenshots/profile.png new file mode 100644 index 000000000..310264a2d Binary files /dev/null and b/priv/static/images/screenshots/profile.png differ diff --git a/priv/static/images/screenshots/share-bounty-private.png b/priv/static/images/screenshots/share-bounty-private.png new file mode 100644 index 000000000..973a0628b Binary files /dev/null and b/priv/static/images/screenshots/share-bounty-private.png differ diff --git a/priv/static/images/screenshots/share-contract.png b/priv/static/images/screenshots/share-contract.png new file mode 100644 index 000000000..b8caa2f6c Binary files /dev/null and b/priv/static/images/screenshots/share-contract.png differ diff --git a/priv/static/images/screenshots/user-dashboard.png b/priv/static/images/screenshots/user-dashboard.png new file mode 100644 index 000000000..81931b66b Binary files /dev/null and b/priv/static/images/screenshots/user-dashboard.png differ diff --git a/priv/static/images/screenshots/user-transactions.png b/priv/static/images/screenshots/user-transactions.png new file mode 100644 index 000000000..0ed455fbf Binary files /dev/null and b/priv/static/images/screenshots/user-transactions.png differ