diff --git a/lib/algora/accounts/accounts.ex b/lib/algora/accounts/accounts.ex
index 642c65587..eaac31fca 100644
--- a/lib/algora/accounts/accounts.ex
+++ b/lib/algora/accounts/accounts.ex
@@ -192,27 +192,6 @@ defmodule Algora.Accounts do
list_developers(handles: ["carver", "jianyang", "aly", "john", "bighead"])
end
- def list_orgs(opts) do
- query =
- from u in User,
- where: u.type == :organization and u.seeded == false and not is_nil(u.provider_login),
- limit: ^Keyword.get(opts, :limit, 100),
- order_by: [desc: u.priority, desc: u.stargazers_count]
-
- query
- |> Repo.all()
- |> Enum.map(fn user ->
- %{
- name: user.name || user.handle,
- handle: user.handle,
- flag: get_flag(user),
- amount: :rand.uniform(100_000),
- tech_stack: Enum.take(user.tech_stack, 6),
- avatar_url: user.avatar_url
- }
- end)
- end
-
def get_users_map(user_ids) when is_list(user_ids) do
Repo.all(from u in User, where: u.id in ^user_ids, select: {u.id, u})
end
diff --git a/lib/algora/accounts/schemas/user.ex b/lib/algora/accounts/schemas/user.ex
index 05a2f70fb..2fd8761a7 100644
--- a/lib/algora/accounts/schemas/user.ex
+++ b/lib/algora/accounts/schemas/user.ex
@@ -92,7 +92,6 @@ defmodule Algora.Accounts.User do
has_many :received_tips, Tip, foreign_key: :recipient_id
has_many :attempts, Algora.Bounties.Attempt
has_many :claims, Algora.Bounties.Claim
- has_many :projects, Algora.Projects.Project
has_many :repositories, Algora.Workspace.Repository
has_many :transactions, Algora.Payments.Transaction, foreign_key: :user_id
has_many :owned_installations, Installation, foreign_key: :owner_id
diff --git a/lib/algora/activities/activities.ex b/lib/algora/activities/activities.ex
index 86b6c8a40..642a1c0d8 100644
--- a/lib/algora/activities/activities.ex
+++ b/lib/algora/activities/activities.ex
@@ -23,14 +23,11 @@ defmodule Algora.Activities do
thread_activities: Algora.Chat.Thread,
contract_activities: Algora.Contracts.Contract,
timesheet_activities: Algora.Contracts.Timesheet,
- application_activities: Algora.Jobs.Application,
- job_activities: Algora.Jobs.Job,
account_activities: Algora.Payments.Account,
customer_activities: Algora.Payments.Customer,
payment_method_activities: Algora.Payments.PaymentMethod,
platform_transaction_activities: Algora.Payments.PlatformTransaction,
transaction_activities: Algora.Payments.Transaction,
- project_activities: Algora.Projects.Project,
review_activities: Algora.Reviews.Project,
installation_activities: Algora.Workplace.Installation,
ticket_activities: Algora.Workspace.Ticket,
@@ -50,7 +47,6 @@ defmodule Algora.Activities do
received_tips: "tip_activities",
identities: "identity_activities",
owned_installations: "installation_activities",
- # projects: "project_activities",
repositories: "repository_activities",
transactions: "transaction_activities"
}
diff --git a/lib/algora/jobs/jobs.ex b/lib/algora/jobs/jobs.ex
deleted file mode 100644
index 5a6d13296..000000000
--- a/lib/algora/jobs/jobs.ex
+++ /dev/null
@@ -1,11 +0,0 @@
-defmodule Algora.Jobs do
- @moduledoc false
- alias Algora.Jobs.Job
- alias Algora.Repo
-
- def create_job(attrs) do
- %Job{}
- |> Job.changeset(attrs)
- |> Repo.insert()
- end
-end
diff --git a/lib/algora/jobs/schemas/application.ex b/lib/algora/jobs/schemas/application.ex
deleted file mode 100644
index 6f24f3cb9..000000000
--- a/lib/algora/jobs/schemas/application.ex
+++ /dev/null
@@ -1,21 +0,0 @@
-defmodule Algora.Jobs.Application do
- @moduledoc false
- use Algora.Schema
-
- alias Algora.Activities.Activity
-
- typed_schema "applications" do
- belongs_to :job, Algora.Jobs.Job
- belongs_to :user, Algora.Accounts.User
-
- has_many :activities, {"application_activities", Activity}, foreign_key: :assoc_id
-
- timestamps()
- end
-
- def changeset(application, attrs) do
- application
- |> cast(attrs, [:job_id, :user_id])
- |> validate_required([:job_id, :user_id])
- end
-end
diff --git a/lib/algora/jobs/schemas/job.ex b/lib/algora/jobs/schemas/job.ex
deleted file mode 100644
index 6aeaa7cb0..000000000
--- a/lib/algora/jobs/schemas/job.ex
+++ /dev/null
@@ -1,20 +0,0 @@
-defmodule Algora.Jobs.Job do
- @moduledoc false
- use Algora.Schema
-
- alias Algora.Activities.Activity
-
- typed_schema "jobs" do
- belongs_to :user, Algora.Accounts.User
-
- has_many :activities, {"job_activities", Activity}, foreign_key: :assoc_id
-
- timestamps()
- end
-
- def changeset(job, attrs) do
- job
- |> cast(attrs, [:user_id])
- |> validate_required([:user_id])
- end
-end
diff --git a/lib/algora/organizations/organizations.ex b/lib/algora/organizations/organizations.ex
index 629745fad..931abbeb0 100644
--- a/lib/algora/organizations/organizations.ex
+++ b/lib/algora/organizations/organizations.ex
@@ -149,7 +149,7 @@ defmodule Algora.Organizations do
Repo.all(
from u in User,
where: u.type == :organization,
- limit: ^Keyword.fetch!(opts, :limit)
+ limit: ^opts[:limit]
)
end
diff --git a/lib/algora/projects/schemas/project.ex b/lib/algora/projects/schemas/project.ex
deleted file mode 100644
index 5907437e6..000000000
--- a/lib/algora/projects/schemas/project.ex
+++ /dev/null
@@ -1,24 +0,0 @@
-defmodule Algora.Projects.Project do
- @moduledoc false
- use Algora.Schema
-
- alias Algora.Activities.Activity
-
- typed_schema "projects" do
- field :name, :string
-
- belongs_to :user, Algora.Accounts.User
- # has_many :milestones, Algora.Projects.Milestone
- # has_many :assignees, Algora.Projects.Assignee
- # has_many :transactions, Algora.Payments.Transaction
- has_many :activities, {"project_activities", Activity}, foreign_key: :assoc_id
-
- timestamps()
- end
-
- def changeset(project, attrs) do
- project
- |> cast(attrs, [:name])
- |> validate_required([:name])
- end
-end
diff --git a/lib/algora_web/live/chat_live.ex b/lib/algora_web/live/chat_live.ex
deleted file mode 100644
index ae9bf32f7..000000000
--- a/lib/algora_web/live/chat_live.ex
+++ /dev/null
@@ -1,472 +0,0 @@
-defmodule AlgoraWeb.ChatLive do
- @moduledoc false
- use AlgoraWeb, :live_view
-
- alias Algora.Accounts
- alias Algora.Accounts.User
-
- def mount(_params, _session, socket) do
- # Get current user details
- current_user = socket.assigns.current_user
-
- # Get all matching devs for the sidebar
- matching_devs = Accounts.list_developers(limit: 20, sort_by_tech_stack: ["Elixir", "Phoenix"])
-
- # In mount function, create static chat histories
- chat_histories = [
- %{
- last_message: "The websocket connection pooling looks good now",
- timestamp: "2m",
- unread: true
- },
- %{
- last_message: "Could you review my PR for the auth middleware?",
- timestamp: "15m",
- unread: false
- },
- %{
- last_message: "Thanks for helping debug that race condition!",
- timestamp: "45m",
- unread: false
- },
- %{
- last_message: "Let's sync about the k8s deployment tomorrow",
- timestamp: "1h",
- unread: true
- },
- %{
- last_message: "The new component library is ready for review",
- timestamp: "3h",
- unread: false
- },
- %{
- last_message: "Just pushed fixes for the GraphQL pagination",
- timestamp: "5h",
- unread: false
- },
- %{
- last_message: "Can you help me with some Elixir pattern matching?",
- timestamp: "8h",
- unread: false
- },
- %{
- last_message: "Docker build is failing on M1 Macs - any ideas?",
- timestamp: "12h",
- unread: true
- },
- %{
- last_message: "Updated the API docs with the new endpoints",
- timestamp: "1d",
- unread: false
- },
- %{
- last_message: "The Redis cache implementation works great!",
- timestamp: "1d",
- unread: false
- },
- %{last_message: "Need help optimizing these DB queries", timestamp: "2d", unread: false},
- %{last_message: "Frontend tests are passing now 🎉", timestamp: "3d", unread: false},
- %{last_message: "How did you handle the WebRTC setup?", timestamp: "4d", unread: false},
- %{
- last_message: "The new CI pipeline reduced build times by 40%",
- timestamp: "5d",
- unread: false
- },
- %{
- last_message: "Let's pair program on the auth flow tomorrow?",
- timestamp: "1w",
- unread: false
- },
- %{
- last_message: "Just deployed the new search functionality",
- timestamp: "2w",
- unread: false
- },
- %{last_message: "The type system caught that bug early!", timestamp: "3w", unread: false},
- %{last_message: "Can you review these database indices?", timestamp: "1mo", unread: false},
- %{
- last_message: "The load balancer config is ready for review",
- timestamp: "2mo",
- unread: false
- },
- %{
- last_message: "Thanks for the help with that memory leak!",
- timestamp: "3mo",
- unread: false
- }
- ]
-
- # Zip the chat histories with matching devs
- chat_threads =
- matching_devs
- |> Enum.zip(chat_histories)
- |> Enum.map(fn {dev, history} ->
- %{
- id: dev.id,
- user: %{
- name: dev.name,
- avatar_url: dev.avatar_url
- },
- last_message: history.last_message,
- timestamp: history.timestamp,
- unread: history.unread
- }
- end)
-
- # Get a random user to chat with
- other_user = Enum.at(matching_devs, 8)
-
- other_user =
- Map.merge(other_user, %{
- timezone: "PST (UTC-8)",
- location: "Vancouver, Canada",
- availability: "Available for work",
- rate: "$120/hr",
- tech_stack: ["Elixir", "Phoenix", "React", "TypeScript", "PostgreSQL"],
- socials: [
- %{name: "GitHub", url: "https://github.com/dev123", icon: "tabler-brand-github"},
- %{name: "Twitter", url: "https://twitter.com/dev123", icon: "tabler-brand-twitter"},
- %{
- name: "LinkedIn",
- url: "https://linkedin.com/in/dev123",
- icon: "tabler-brand-linkedin"
- },
- %{name: "Website", url: "https://dev123.com", icon: "tabler-world"}
- ]
- })
-
- messages = [
- %{
- id: 1,
- sender: current_user,
- content:
- "yo! saw you've worked with Phoenix LiveView before. we're building a real-time collab tool and could use your expertise",
- sent_at: "11:30 AM",
- date: "Monday",
- is_self: true,
- avatar_url: current_user.avatar_url
- },
- %{
- id: 2,
- sender: other_user,
- content: "hey! yeah I've built a few things with LiveView. what kind of collab tool are you working on?",
- sent_at: "11:45 AM",
- date: "Monday",
- is_self: false,
- avatar_url: other_user.avatar_url
- },
- %{
- id: 3,
- sender: current_user,
- content:
- "it's like figma but for devs - shared coding environment with real-time cursors, chat, etc. biggest challenge right now is handling presence with 1000+ concurrent users",
- sent_at: "2:30 PM",
- date: "Yesterday",
- is_self: true,
- avatar_url: current_user.avatar_url
- },
- %{
- id: 4,
- sender: other_user,
- content:
- "oh nice! yeah Phoenix Presence would work well for that. I did something similar with LiveView for a collaborative whiteboard. had to do some tricks with debouncing to handle the cursor updates efficiently",
- sent_at: "3:15 PM",
- date: "Yesterday",
- is_self: false,
- avatar_url: other_user.avatar_url
- },
- %{
- id: 5,
- sender: current_user,
- content:
- "that's exactly what we need! mind taking a look at our presence.ex module? getting some weird race conditions when users disconnect",
- sent_at: "10:30 AM",
- date: "Today",
- is_self: true,
- avatar_url: current_user.avatar_url
- },
- %{
- id: 6,
- sender: other_user,
- content:
- "sure, drop a gist link. also check out :pg2 vs :pg - we had to switch because of similar issues at scale",
- sent_at: "10:32 AM",
- date: "Today",
- is_self: false,
- avatar_url: other_user.avatar_url
- },
- %{
- id: 7,
- sender: current_user,
- content: "awesome, here's the gist: https://gist.github.com/... also curious how you handled the pubsub sharding",
- sent_at: "10:35 AM",
- date: "Today",
- is_self: true,
- avatar_url: current_user.avatar_url
- }
- ]
-
- if connected?(socket) do
- {:ok,
- socket
- |> assign(
- messages: messages,
- current_user: current_user,
- other_user: other_user,
- chat_threads: chat_threads
- )
- |> push_event("scroll-to-bottom", %{})}
- else
- {:ok,
- assign(socket,
- messages: messages,
- current_user: current_user,
- other_user: other_user,
- chat_threads: chat_threads
- )}
- end
- end
-
- def render(assigns) do
- ~H"""
-
-
-
-
-
-
Messages
-
- <.button variant="ghost" size="icon">
- <.icon name="tabler-filter" class="h-4 w-4" />
-
- <.button variant="ghost" size="icon">
- <.icon name="tabler-edit" class="h-4 w-4" />
-
-
-
-
- <.icon
- name="tabler-search"
- class="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-muted-foreground"
- />
- <.input
- type="search"
- name="search"
- placeholder="Search messages..."
- class="w-full pl-9"
- value=""
- />
-
-
- <.scroll_area class="flex-1">
-
- <%= for thread <- @chat_threads do %>
-
-
- <.avatar class="h-12 w-12">
- <.avatar_image src={thread.user.avatar_url} />
- <.avatar_fallback>{Algora.Util.initials(thread.user.name)}
-
-
-
-
-
-
- {thread.user.name}
- {thread.timestamp}
-
-
-
- {thread.last_message}
-
- <%= if thread.unread do %>
-
- <% end %>
-
-
-
- <% end %>
-
-
-
-
-
-
-
-
- <.avatar>
- <.avatar_image src={@other_user.avatar_url} alt="User avatar" />
- <.avatar_fallback>{Algora.Util.initials(@other_user.name)}
-
-
-
-
-
-
{@other_user.name}
-
Active now
-
-
-
- <.button variant="ghost" size="icon">
- <.icon name="tabler-phone" class="h-4 w-4" />
-
- <.button variant="ghost" size="icon">
- <.icon name="tabler-video" class="h-4 w-4" />
-
- <.button variant="ghost" size="icon">
- <.icon name="tabler-dots-vertical" class="h-4 w-4" />
-
-
-
- <.scroll_area
- class="flex flex-1 flex-col-reverse p-4"
- id="messages-container"
- phx-hook="ScrollToBottom"
- >
-
- <%= for {date, messages} <- Enum.group_by(@messages, & &1.date) do %>
-
-
-
- <%= for message <- messages do %>
-
- <.avatar class="h-8 w-8">
- <.avatar_image src={message.avatar_url} />
- <.avatar_fallback>
- {Algora.Util.initials(message.sender.name)}
-
-
-
- {message.content}
-
- {message.sent_at}
-
-
-
- <% end %>
- <% end %>
-
-
-
-
-
-
-
-
-
-
- <.avatar class="h-20 w-20">
- <.avatar_image src={@other_user.avatar_url} />
- <.avatar_fallback>{Algora.Util.initials(@other_user.name)}
-
-
- {@other_user.name} {@other_user.flag}
-
-
@{User.handle(@other_user)}
-
-
- <%= for tech <- Enum.take(@other_user.tech_stack || [], 3) do %>
-
- {tech}
-
- <% end %>
- <%= if length(@other_user.tech_stack || []) > 3 do %>
-
- +{length(@other_user.tech_stack) - 3} more
-
- <% end %>
-
-
-
-
-
- <.icon name="tabler-map-pin" class="h-4 w-4" />
- {@other_user.location}
-
-
- <.icon name="tabler-clock" class="h-4 w-4" />
- {@other_user.timezone}
-
-
- <.icon name="tabler-circle-check" class="h-4 w-4 text-success" />
- {@other_user.availability}
-
-
- <.icon name="tabler-currency-dollar" class="h-4 w-4" />
- {@other_user.rate}
-
-
-
-
-
-
-
-
- <.button variant="outline" navigate={User.url(@other_user)} class="w-full">
- View Full Profile <.icon name="tabler-arrow-right" class="ml-2 h-4 w-4" />
-
-
-
-
- """
- end
-
- def handle_event("send_message", %{"message" => content}, socket) do
- new_message = %{
- id: System.unique_integer([:positive]),
- sender: socket.assigns.current_user,
- content: content,
- sent_at: Time.utc_now() |> Time.to_string() |> String.slice(0..4),
- is_self: true,
- avatar_url: socket.assigns.current_user.avatar_url
- }
-
- {:noreply, update(socket, :messages, &(&1 ++ [new_message]))}
- end
-
- def handle_event("scroll-to-bottom", _, socket) do
- {:noreply, push_event(socket, "scroll-to-bottom", %{})}
- end
-end
diff --git a/lib/algora_web/live/claim_live.ex b/lib/algora_web/live/claim_live.ex
index c5f14b526..d35d4b254 100644
--- a/lib/algora_web/live/claim_live.ex
+++ b/lib/algora_web/live/claim_live.ex
@@ -195,15 +195,6 @@ defmodule AlgoraWeb.ClaimLive do
|> assign_line_items()}
end
- def handle_event("split_bounty", _params, socket) do
- # TODO: Implement split bounty
- Logger.error(
- "Attempt to split bounty #{socket.assigns.target.repository.user.provider_login}/#{socket.assigns.target.repository.name}#{socket.assigns.target.number}"
- )
-
- {:noreply, socket}
- end
-
def handle_event("pay_with_stripe", %{"reward_bounty_form" => params}, socket) do
changeset = RewardBountyForm.changeset(%RewardBountyForm{}, params)
diff --git a/lib/algora_web/live/community/dashboard_live.ex b/lib/algora_web/live/community/dashboard_live.ex
deleted file mode 100644
index 2b348c8ce..000000000
--- a/lib/algora_web/live/community/dashboard_live.ex
+++ /dev/null
@@ -1,345 +0,0 @@
-defmodule AlgoraWeb.Community.DashboardLive do
- @moduledoc false
- use AlgoraWeb, :live_view
-
- import AlgoraWeb.Components.Achievement
- import AlgoraWeb.Components.Bounties
- import AlgoraWeb.Components.Users
- import Ecto.Changeset
-
- alias Algora.Accounts
- alias Algora.Bounties
- alias Algora.Contracts
- alias Algora.Types.USD
- alias Algora.Validations
- alias Algora.Workspace
-
- require Logger
-
- defmodule BountyForm do
- @moduledoc false
- use Ecto.Schema
-
- import Ecto.Changeset
-
- embedded_schema do
- field :url, :string
- field :amount, USD
-
- embeds_one :ticket_ref, TicketRef, primary_key: false do
- field :owner, :string
- field :repo, :string
- field :number, :integer
- field :type, :string
- end
- end
-
- def changeset(form, attrs \\ %{}) do
- form
- |> cast(attrs, [:url, :amount])
- |> validate_required([:url, :amount])
- |> Validations.validate_money_positive(:amount)
- |> Validations.validate_ticket_ref(:url, :ticket_ref)
- end
- end
-
- defmodule TipForm do
- @moduledoc false
- use Ecto.Schema
-
- import Ecto.Changeset
-
- embedded_schema do
- field :github_handle, :string
- field :amount, USD
- end
-
- def changeset(form, attrs \\ %{}) do
- form
- |> cast(attrs, [:github_handle, :amount])
- |> validate_required([:github_handle, :amount])
- |> Validations.validate_money_positive(:amount)
- end
- end
-
- def mount(_params, _session, socket) do
- users =
- socket.assigns.current_user.tech_stack
- |> List.first()
- |> Accounts.list_community()
- |> Enum.take(6)
-
- if connected?(socket) do
- Bounties.subscribe()
- end
-
- {:ok,
- socket
- |> assign(:bounty_form, to_form(BountyForm.changeset(%BountyForm{}, %{})))
- |> assign(:tip_form, to_form(TipForm.changeset(%TipForm{}, %{})))
- |> assign(:users, users)
- |> assign_bounties()
- |> assign_achievements()}
- end
-
- def render(assigns) do
- ~H"""
-
-
- <.section>
-
- {create_bounty(assigns)}
- {create_tip(assigns)}
-
-
-
- <.section title="Open bounties" subtitle="Bounties for you" link={~p"/bounties"}>
- <%= if Enum.empty?(@bounties) do %>
- <.card class="rounded-lg bg-card py-12 text-center lg:rounded-[2rem]">
- <.card_header>
-
- <.icon name="tabler-diamond" class="h-8 w-8 text-muted-foreground" />
-
- <.card_title>No bounties yet
- <.card_description>
- Open bounties will appear here once created
-
-
-
- <% else %>
- <.bounties bounties={@bounties} />
- <% end %>
-
-
- <.section
- :if={@users != []}
- title="Community"
- subtitle="Meet the Algora community"
- link={~p"/community"}
- >
-
- <.users users={@users} />
-
-
-
-
- {sidebar(assigns)}
- """
- end
-
- defp create_bounty(assigns) do
- ~H"""
- <.card>
- <.card_header>
-
- <.icon name="tabler-diamond" class="h-8 w-8" />
-
Post a bounty
-
-
- <.card_content>
- <.simple_form for={@bounty_form} phx-submit="create_bounty">
-
- <.input
- label="URL"
- field={@bounty_form[:url]}
- placeholder="https://github.com/swift-lang/swift/issues/1337"
- />
- <.input label="Amount" icon="tabler-currency-dollar" field={@bounty_form[:amount]} />
-
- Tip:
- You can also create bounties directly on
- GitHub by commenting /bounty $100
- on any issue.
-
-
- <.button>Submit
-
-
-
-
-
- """
- end
-
- defp create_tip(assigns) do
- ~H"""
- <.card>
- <.card_header>
-
- <.icon name="tabler-gift" class="h-8 w-8" />
-
Tip a developer
-
-
- <.card_content>
- <.simple_form for={@tip_form} phx-submit="create_tip">
-
- <.input label="GitHub handle" field={@tip_form[:github_handle]} placeholder="jsmith" />
- <.input label="Amount" icon="tabler-currency-dollar" field={@tip_form[:amount]} />
-
- Tip:
- You can also create tips directly on
- GitHub by commenting /tip $100 @username
- on any pull request.
-
-
- <.button>Submit
-
-
-
-
-
- """
- end
-
- defp sidebar(assigns) do
- ~H"""
-
- """
- end
-
- def handle_event("create_bounty", %{"bounty_form" => params}, socket) do
- changeset =
- %BountyForm{}
- |> BountyForm.changeset(params)
- |> Map.put(:action, :validate)
-
- amount = get_field(changeset, :amount)
- ticket_ref = get_field(changeset, :ticket_ref)
-
- with %{valid?: true} <- changeset,
- {:ok, _bounty} <-
- Bounties.create_bounty(%{
- creator: socket.assigns.current_user,
- owner: socket.assigns.current_user,
- amount: amount,
- ticket_ref: ticket_ref
- }) do
- {:noreply,
- socket
- |> assign_achievements()
- |> put_flash(:info, "Bounty created")}
- else
- %{valid?: false} ->
- {:noreply, assign(socket, :bounty_form, to_form(changeset))}
-
- {:error, :already_exists} ->
- {:noreply, put_flash(socket, :warning, "You have already created a bounty for this ticket")}
-
- {:error, _reason} ->
- {:noreply, put_flash(socket, :error, "Something went wrong")}
- end
- end
-
- def handle_event("create_tip", %{"tip_form" => params}, socket) do
- changeset =
- %TipForm{}
- |> TipForm.changeset(params)
- |> Map.put(:action, :validate)
-
- with %{valid?: true} <- changeset,
- {:ok, token} <- Accounts.get_access_token(socket.assigns.current_user),
- {:ok, recipient} <- Workspace.ensure_user(token, get_field(changeset, :github_handle)),
- {:ok, checkout_url} <-
- Bounties.create_tip(%{
- creator: socket.assigns.current_user,
- owner: socket.assigns.current_user,
- recipient: recipient,
- amount: get_field(changeset, :amount)
- }) do
- {:noreply, redirect(socket, external: checkout_url)}
- else
- %{valid?: false} ->
- {:noreply, assign(socket, :tip_form, to_form(changeset))}
-
- {:error, reason} ->
- Logger.error("Failed to create tip: #{inspect(reason)}")
- {:noreply, put_flash(socket, :error, "Something went wrong")}
- end
- end
-
- def handle_info(:bounties_updated, socket) do
- {:noreply, assign_bounties(socket)}
- end
-
- defp assign_bounties(socket) do
- bounties =
- Bounties.list_bounties(
- status: :open,
- tech_stack: socket.assigns.current_user.tech_stack,
- limit: 100
- )
-
- assign(socket, :bounties, Enum.take(bounties, 6))
- end
-
- defp assign_achievements(socket) do
- tech = List.first(socket.assigns.current_user.tech_stack)
-
- status_fns = [
- {&personalize_status/1, "Personalize Algora", nil},
- {&create_bounty_status/1, "Create a bounty", nil},
- {&reward_bounty_status/1, "Reward a bounty", nil},
- {&begin_collaboration_status/1, "Contract a #{tech} developer", nil},
- {&complete_first_contract_status/1, "Complete a contract", nil}
- ]
-
- {achievements, _} =
- Enum.reduce_while(status_fns, {[], false}, fn {status_fn, name, path}, {acc, found_current} ->
- status = status_fn.(socket.assigns.current_user)
-
- result =
- cond do
- found_current -> {acc ++ [%{status: status, name: name, path: path}], found_current}
- status == :completed -> {acc ++ [%{status: status, name: name, path: path}], false}
- true -> {acc ++ [%{status: :current, name: name, path: path}], true}
- end
-
- {:cont, result}
- end)
-
- assign(socket, :achievements, achievements)
- end
-
- defp personalize_status(_socket), do: :completed
-
- defp create_bounty_status(user) do
- case Bounties.list_bounties(owner_id: user.id, limit: 1) do
- [] -> :upcoming
- _ -> :completed
- end
- end
-
- defp reward_bounty_status(user) do
- case Bounties.list_bounties(owner_id: user.id, status: :paid, limit: 1) do
- [] -> :upcoming
- _ -> :completed
- end
- end
-
- defp begin_collaboration_status(user) do
- case Contracts.list_contracts(client_id: user.id, active_or_paid?: true, limit: 1) do
- [] -> :upcoming
- _ -> :completed
- end
- end
-
- defp complete_first_contract_status(user) do
- case Contracts.list_contracts(client_id: user.id, status: :paid, limit: 1) do
- [] -> :upcoming
- _ -> :completed
- end
- end
-end
diff --git a/lib/algora_web/live/companies_live.ex b/lib/algora_web/live/companies_live.ex
deleted file mode 100644
index 4cfdb1726..000000000
--- a/lib/algora_web/live/companies_live.ex
+++ /dev/null
@@ -1,796 +0,0 @@
-defmodule AlgoraWeb.CompaniesLive do
- @moduledoc false
- use AlgoraWeb, :live_view
-
- alias Algora.Accounts
-
- @impl true
- def mount(_params, _session, socket) do
- {:ok,
- socket
- |> assign(:featured_devs, Accounts.list_featured_developers())
- |> assign(:stats, fetch_stats())
- |> assign(:mobile_menu_open, false)
- |> assign(:expanded_faq, nil)
- |> assign(:features, features())
- |> assign(:faqs, faqs())
- |> assign(:testimonials, testimonials())}
- end
-
- @impl true
- def handle_event("toggle-mobile-menu", _, socket) do
- {:noreply, assign(socket, :mobile_menu_open, !socket.assigns.mobile_menu_open)}
- end
-
- @impl true
- def handle_event("toggle-faq", %{"id" => id}, socket) do
- {:noreply, assign(socket, expanded_faq: if(socket.assigns.expanded_faq == id, do: nil, else: id))}
- end
-
- @impl true
- def render(assigns) do
- ~H"""
-
-
-
-
-
-
-
-
-
-
- Hire Elite Open Source Developers
-
-
- Access a curated network of top open source contributors. From quick bug fixes to full-time hires,
- find the perfect developer for your needs.
-
-
- <.link
- navigate={~p"/onboarding/org"}
- class="rounded-md bg-indigo-500 px-8 py-3 text-lg font-semibold text-white shadow-sm hover:bg-indigo-400"
- >
- Post Your First Job
-
-
- Learn more →
-
-
-
-
-
- <%= for stat <- @stats do %>
-
-
{stat.label}
-
- {stat.value}
-
-
- <% end %>
-
-
-
-
-
-
- Seamlessly Integrates With Your Tools
-
-
-
-
-
-
-
-
Deploy faster
-
- Everything you need to manage developers
-
-
- From finding the right talent to managing payments, we've got you covered with a complete suite of tools.
-
-
-
-
- <%= for feature <- features() do %>
-
-
- <.icon name={feature.icon} class="h-5 w-5 flex-none text-indigo-400" />
- {feature.name}
-
-
- {feature.description}
-
-
- <% end %>
-
-
-
-
-
-
-
-
-
Getting Started
-
- How Algora Works
-
-
- Get started in minutes and find the perfect developer for your project.
-
-
-
-
- <%= for {step, i} <- Enum.with_index(how_it_works()) do %>
-
-
-
- {i + 1}
-
- {step.name}
-
-
- {step.description}
-
-
- <% end %>
-
-
-
-
-
-
-
-
-
Pricing
-
- Simple, transparent pricing
-
-
- Choose the plan that best fits your needs. All plans include access to our full platform.
-
-
-
- <%= for plan <- pricing_plans() do %>
-
-
-
-
{plan.name}
- <%= if plan.popular do %>
-
- Most popular
-
- <% end %>
-
-
{plan.description}
-
-
- {plan.price}
-
- <%= if plan.period do %>
-
- /{plan.period}
-
- <% end %>
-
-
- <%= for feature <- plan.features do %>
-
- <.icon name="tabler-check" class="h-5 w-5 flex-none text-indigo-400" />
- {feature}
-
- <% end %>
-
-
- <.link
- navigate={~p"/onboarding/org"}
- class="mt-8 block rounded-md bg-indigo-500 px-3 py-2 text-center text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500"
- >
- Get started today
-
-
- <% end %>
-
-
-
-
-
-
-
-
Testimonials
-
- Trusted by companies worldwide
-
-
-
- <%= for {testimonial, index} <- Enum.with_index(@testimonials) do %>
-
-
- <%= if index == 4 do %>
-
-
-
-
- {testimonial.name}
-
-
{testimonial.role}
-
{testimonial.company}
-
-
-
- "{testimonial.quote}"
-
-
-
-
-
-
- Their Journey with Algora
-
-
- <%= for paragraph <- testimonial.description do %>
-
{paragraph}
- <% end %>
-
-
- Impact Summary
-
-
- <%= for {label, value} <- testimonial.results do %>
-
-
- {label}
-
-
- {value}
-
-
- <% end %>
-
-
-
- <% else %>
-
-
-
-
-
- {testimonial.name}
-
-
- {testimonial.role}
-
-
- {testimonial.company}
-
-
-
-
-
- "{testimonial.quote}"
-
-
-
-
-
-
- Their Journey with Algora
-
-
- <%= for paragraph <- testimonial.description do %>
-
{paragraph}
- <% end %>
-
-
- Impact Summary
-
-
- <%= for {label, value} <- testimonial.results do %>
-
-
- {label}
-
-
- {value}
-
-
- <% end %>
-
-
-
- <% end %>
-
-
- <% end %>
-
-
-
-
-
-
-
- Frequently asked questions
-
-
- <%= for faq <- faqs() do %>
-
-
-
- {faq.question}
-
-
-
- {faq.answer}
-
-
- <% end %>
-
-
-
-
-
-
-
-
Case Studies
-
- Success Stories
-
-
- See how other companies have succeeded with Algora
-
-
-
- <%= for case_study <- case_studies() do %>
-
-
-
-
-
-
-
-
-
{case_study.date}
-
-
-
-
-
-
- {case_study.author}
-
-
-
-
-
- <% end %>
-
-
-
-
-
- """
- end
-
- defp features do
- [
- %{
- name: "Global Talent Pool",
- icon: "tabler-world",
- description: "Access top developers from around the world, pre-vetted and ready to contribute to your projects."
- },
- %{
- name: "Seamless Integration",
- icon: "tabler-puzzle",
- description: "Our platform integrates with your existing tools and workflows, making team expansion effortless."
- },
- %{
- name: "Quality Assurance",
- icon: "tabler-shield-check",
- description: "Every developer is thoroughly vetted through technical assessments and real-world projects."
- },
- %{
- name: "Flexible Engagement",
- icon: "tabler-clock",
- description: "Work with developers on your terms - full-time, part-time, or project-based."
- },
- %{
- name: "Cost Effective",
- icon: "tabler-coin",
- description: "Save on recruitment costs and overhead while accessing top-tier talent."
- },
- %{
- name: "24/7 Support",
- icon: "tabler-headset",
- description: "Our team is always available to help you with any questions or concerns."
- }
- ]
- end
-
- defp how_it_works do
- [
- %{
- name: "Create Your Project",
- description: "Describe your needs, set your budget, and specify your requirements."
- },
- %{
- name: "Get Matched",
- description: "Our AI matches you with the best developers for your project based on tech stack and experience."
- },
- %{
- name: "Start Working",
- description: "Begin collaboration immediately with our integrated project management tools."
- }
- ]
- end
-
- defp pricing_plans do
- [
- %{
- name: "Basic",
- price: "Free",
- description: "Perfect for small projects and individual developers.",
- period: nil,
- popular: false,
- features: [
- "Up to 3 active projects",
- "Basic developer matching",
- "Standard support",
- "Community access"
- ]
- },
- %{
- name: "Pro",
- price: "$99",
- description: "Everything you need for growing teams.",
- period: "month",
- popular: true,
- features: [
- "Unlimited projects",
- "Priority matching",
- "24/7 priority support",
- "Advanced analytics",
- "Custom integrations"
- ]
- },
- %{
- name: "Enterprise",
- price: "Custom",
- description: "Dedicated support and custom solutions for large organizations.",
- period: nil,
- popular: false,
- features: [
- "Custom solutions",
- "Dedicated account manager",
- "SLA guarantees",
- "Advanced security features",
- "Custom reporting"
- ]
- }
- ]
- end
-
- defp testimonials do
- [
- %{
- name: "Sarah Chen",
- role: "CEO & Co-founder",
- company: "TechCorp",
- avatar: "https://images.unsplash.com/photo-1494790108377-be9c29b29330",
- quote:
- "Algora has transformed how we build our engineering team. The quality of developers and the speed at which we were able to scale our team exceeded our expectations.",
- description: [
- "TechCorp came to Algora looking to scale their engineering team rapidly without compromising on quality.",
- "Within weeks, they were able to onboard senior developers who had significant open source contributions in their tech stack.",
- "The seamless integration with their existing workflows meant new hires could start contributing immediately."
- ],
- results: [
- {"Developers Hired", "12"},
- {"Time to Hire", "< 2 weeks"},
- {"Cost Savings", "45%"},
- {"Projects Completed", "28"}
- ]
- },
- %{
- name: "Michael Rodriguez",
- role: "CTO",
- company: "StartupX",
- avatar: "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e",
- quote:
- "The developers we've hired through Algora have been exceptional. They don't just write code - they contribute meaningful solutions and have become integral parts of our team.",
- description: [
- "StartupX needed to build a team of specialized developers for their AI-powered platform.",
- "Through Algora, they found developers who not only had the technical skills but also brought valuable experience from similar projects.",
- "The quality of contributions led them to expand their initial hiring plans and build out entire new features."
- ],
- results: [
- {"Team Growth", "3x"},
- {"Sprint Velocity", "+65%"},
- {"Code Quality", "98%"},
- {"Retention Rate", "95%"}
- ]
- },
- %{
- name: "Emily Thompson",
- role: "VP Engineering",
- company: "ScaleUp Inc",
- avatar: "https://images.unsplash.com/photo-1438761681033-6461ffad8d80",
- quote:
- "What impressed me most was how quickly the developers were able to adapt to our codebase and start making meaningful contributions. The quality of talent on Algora is outstanding.",
- description: [
- "ScaleUp Inc was struggling to find developers who could handle their complex microservices architecture.",
- "Algora matched them with developers who had extensive experience with distributed systems and cloud infrastructure.",
- "The impact was immediate, with new team members improving system reliability and deployment processes within their first month."
- ],
- results: [
- {"Open Source Contributions", "150+"},
- {"Team Expansion", "4x"},
- {"Project Delivery", "2x faster"},
- {"Cost Efficiency", "40%"}
- ]
- },
- %{
- name: "David Park",
- role: "Head of Engineering",
- company: "InnovateAI",
- avatar: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d",
- quote:
- "Finding ML engineers with open source experience was a challenge until we discovered Algora. The platform's matching algorithm helped us build a world-class AI team in record time.",
- description: [
- "InnovateAI needed specialized machine learning engineers with experience in production systems.",
- "Through Algora's network, they connected with developers who had contributed to major ML frameworks.",
- "The new team members helped revolutionize their ML pipeline and significantly improve model performance."
- ],
- results: [
- {"Specialized Hires", "8"},
- {"Product Launch", "3 months faster"},
- {"Model Accuracy", "+28%"},
- {"Infrastructure Savings", "52%"}
- ]
- },
- %{
- name: "Alex Rivera",
- role: "Engineering Director",
- company: "CloudScale Systems",
- quote:
- "Algora didn't just help us hire developers - they helped us build a culture of open source contribution that has transformed our entire engineering organization.",
- description: [
- "CloudScale Systems needed to rapidly scale their infrastructure team while maintaining their high standards for code quality.",
- "Through Algora, they found developers with significant contributions to major cloud infrastructure projects.",
- "The team's expertise in cloud-native technologies helped them reduce deployment times and improve system reliability."
- ],
- results: [
- {"Infrastructure Costs", "-60%"},
- {"Deployment Time", "90% faster"},
- {"System Reliability", "99.999%"},
- {"Team Productivity", "3x increase"}
- ]
- }
- ]
- end
-
- defp faqs do
- [
- %{
- question: "How does your developer vetting process work?",
- answer:
- "Our vetting process is comprehensive and includes multiple stages: technical skills assessment, code quality review, communication evaluation, and background verification. We accept only the top 1% of applicants to ensure the highest quality talent for your team."
- },
- %{
- question: "What if I'm not satisfied with a developer's performance?",
- answer:
- "We offer a 100% satisfaction guarantee. If you're not completely satisfied with a developer's performance, we'll work with you to find a replacement at no additional cost. Our priority is ensuring a perfect fit for your team."
- },
- %{
- question: "How quickly can I add developers to my team?",
- answer:
- "Most companies are able to start working with their new developers within 1-2 weeks. For specialized roles or specific technology requirements, it might take up to 3 weeks to ensure the perfect match."
- },
- %{
- question: "What types of developers are available on your platform?",
- answer:
- "We have developers across all major technologies and specializations including: Frontend (React, Vue, Angular), Backend (Node.js, Python, Ruby, Elixir), Mobile (iOS, Android, React Native), DevOps, Data Science, and more."
- },
- %{
- question: "How do you handle intellectual property and NDAs?",
- answer:
- "We take intellectual property very seriously. All developers sign comprehensive NDAs and IP assignment agreements. We can also work with your legal team to implement any additional agreements specific to your company's needs."
- },
- %{
- question: "What are your pricing models?",
- answer:
- "We offer flexible pricing models to suit different needs: hourly rates for project-based work, monthly retainers for ongoing engagements, and custom enterprise packages for larger teams. Contact us for detailed pricing based on your specific requirements."
- }
- ]
- end
-
- defp case_studies do
- [
- %{
- title: "How StartupX Scaled Their Engineering Team",
- href: "#",
- description: "StartupX needed to double their engineering team in 3 months. See how they did it with Algora.",
- date: "Mar 16, 2024",
- author: "Tom Cook",
- author_image: "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e",
- image:
- "https://images.unsplash.com/photo-1496128858413-b36217c2ce36?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=3603&q=80"
- },
- %{
- title: "TechCorp's Journey to Remote-First Development",
- href: "#",
- description: "How TechCorp built a distributed engineering team across 12 countries.",
- date: "Mar 10, 2024",
- author: "Sarah Chen",
- author_image: "https://images.unsplash.com/photo-1494790108377-be9c29b29330",
- image:
- "https://images.unsplash.com/photo-1547586696-ea22b4d4235d?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=3270&q=80"
- },
- %{
- title: "DevCo's Open Source Success Story",
- href: "#",
- description: "How DevCo leveraged open source talent to accelerate their product development.",
- date: "Mar 5, 2024",
- author: "Emily Thompson",
- author_image: "https://images.unsplash.com/photo-1438761681033-6461ffad8d80",
- image:
- "https://images.unsplash.com/photo-1492724441997-5dc865305da7?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=3270&q=80"
- }
- ]
- end
-
- defp fetch_stats do
- [
- %{label: "Average Response Time", value: "4h"},
- %{label: "Vetted Developers", value: "500+"},
- %{label: "Success Rate", value: "94%"},
- %{label: "Cost Savings", value: "40%"}
- ]
- end
-end
diff --git a/lib/algora_web/live/dev_live.ex b/lib/algora_web/live/dev_live.ex
deleted file mode 100644
index 3158fca9c..000000000
--- a/lib/algora_web/live/dev_live.ex
+++ /dev/null
@@ -1,818 +0,0 @@
-defmodule AlgoraWeb.DevLive do
- @moduledoc false
- use AlgoraWeb, :live_view
-
- alias Algora.Accounts.User
-
- def mount(_params, _session, socket) do
- project = %{
- title: "Looking for an Elixir developer to build a real-time chat application",
- description: "Build a real-time chat application using Phoenix and LiveView.",
- tech_stack: ["Elixir", "Phoenix", "PostgreSQL", "TailwindCSS"],
- country: socket.assigns.current_country
- }
-
- nav_items = [
- %{
- icon: "tabler-home",
- label: "Dashboard",
- href: "#",
- active: true
- },
- %{
- icon: "tabler-diamond",
- label: "Bountes",
- href: "#",
- active: false
- },
- %{
- icon: "tabler-file",
- label: "Documents",
- href: "#",
- active: false
- },
- %{
- icon: "tabler-users",
- label: "Team",
- href: "#",
- active: false
- }
- ]
-
- footer_nav_items = [
- %{
- icon: "tabler-settings",
- label: "Settings",
- href: "#"
- }
- ]
-
- user_menu_items = [
- %{label: "My Account", href: "#", divider: true},
- %{label: "Settings", href: "#"},
- %{label: "Support", href: "#", divider: true},
- %{label: "Logout", href: "#"}
- ]
-
- filter_menu_items = [
- %{label: "Fulfilled", href: "#"},
- %{label: "Declined", href: "#"},
- %{label: "Refunded", href: "#"}
- ]
-
- time_periods = [
- %{label: "Week", value: "week"},
- %{label: "Month", value: "month"},
- %{label: "Year", value: "year"}
- ]
-
- # matching_devs =
- # Accounts.list_developers(
- # limit: 5,
- # sort_by_country: project.country,
- # sort_by_tech_stack: project.tech_stack
- # )
-
- # bounties = Bounties.list_bounties(limit: 8)
-
- {:ok,
- assign(socket,
- page_title: "Project",
- project: project,
- nav_items: nav_items,
- footer_nav_items: footer_nav_items,
- user_menu_items: user_menu_items,
- filter_menu_items: filter_menu_items,
- time_periods: time_periods,
- active_period: "week",
- matching_devs: [],
- bounties: []
- )}
- end
-
- def render(assigns) do
- ~H"""
-
-
-
-
-
-
- <%= for item <- @nav_items do %>
-
- <% end %>
-
-
- <%= for item <- @footer_nav_items do %>
-
- <% end %>
-
-
-
-
-
-
-
-
-
-
-
- Toggle Menu
-
-
-
-
-
-
-
-
-
- {@current_org.name}
-
-
-
-
-
-
-
-
-
-
- Projects
-
-
-
-
-
-
-
-
-
- {@project.title}
-
-
-
-
-
-
-
-
-
-
- My Account
-
-
-
- <%= for item <- @user_menu_items do %>
- <%= if item[:divider] do %>
-
- <% end %>
-
- {item.label}
-
- <% end %>
-
-
-
-
-
-
-
-
-
-
-
-
-
- {@project.title}
-
-
-
- <%= for tech <- @project.tech_stack do %>
-
- {tech}
-
- <% end %>
-
-
-
-
- <.icon name="tabler-clock" class="h-4 w-4" /> Posted March 15, 2024
-
-
- <.icon name="tabler-world" class="h-4 w-4" /> {@project.country}
-
-
-
- <%= if @project[:hourly_rate] do %>
-
-
- {Money.to_string!(@project.hourly_rate)}/hour
-
-
- Hourly Rate
-
-
- <% end %>
-
-
-
-
-
-
- <.icon name="tabler-file-description" class="h-5 w-5" />
-
Project Description
-
-
- Add details on requirements, timeline, and expectations.
-
-
- Add Description
-
-
-
-
-
-
- <.icon name="tabler-file-upload" class="h-5 w-5" />
-
Documents
-
-
- Upload NDA, IP agreements, and other legal documents.
-
-
- Upload Documents
-
-
-
-
-
- <%!--
-
- <%= for period <- @time_periods do %>
-
- <%= period.label %>
-
- <% end %>
-
-
-
-
-
-
-
- <%= for item <- @filter_menu_items do %>
-
- <%= item.label %>
-
- <% end %>
-
-
-
-
-
- <.icon name="tabler-file-export" class="h-3.5 w-3.5" />
- Export
-
-
-
--%>
-
-
-
-
- Bounties
-
-
- Bounties linked to your project
-
-
-
-
-
-
-
- Ticket
-
-
- Owner
-
-
- Tech Stack
-
-
- Posted
-
-
- Bounty
-
-
-
-
- <%= if @bounties == [] do %>
-
-
-
-
- <.icon name="tabler-plus" class="h-6 w-6 text-primary" />
-
-
No Bounties Yet
-
- Create your first bounty to start attracting developers to your project.
-
-
- <.icon name="tabler-plus" class="mr-2 h-4 w-4" /> Add Bounty
-
-
-
-
- <% else %>
- <%= for bounty <- @bounties do %>
-
-
- {bounty.ticket.title}
-
- {bounty.ticket.owner}/{bounty.ticket.repo} #{bounty.ticket.number}
-
-
-
-
-
- {bounty.owner.name}
-
-
-
-
- <%= for tech <- bounty.tech_stack || [] do %>
-
- {tech}
-
- <% end %>
-
-
-
- {Calendar.strftime(bounty.inserted_at, "%Y-%m-%d")}
-
-
- {Money.to_string!(bounty.amount)}
-
-
- <% end %>
- <% end %>
-
-
-
-
-
-
-
-
-
-
-
-
- <.icon name="tabler-users-plus" class="h-6 w-6 text-primary" />
-
-
Invite Developers
-
- Share this project with developers in your network or invite them directly.
-
-
-
- Invite Developers
-
-
- Share Link
-
-
-
-
-
-
- <%= if @matching_devs != [] do %>
-
-
-
- Matching Developers
-
-
-
- <%= for dev <- @matching_devs do %>
-
-
-
-
-
-
- {dev.name} {dev.flag}
-
-
- @{User.handle(dev)}
-
-
-
-
Earned
-
- {Money.to_string!(dev.total_earned)}
-
-
-
-
-
- <%= for tech <- dev.tech_stack do %>
-
- {tech}
-
- <% end %>
-
-
-
- <% end %>
-
-
-
- <% end %>
-
-
-
-
- """
- end
-
- def handle_event("select_period", %{"period" => period}, socket) do
- {:noreply, assign(socket, active_period: period)}
- end
-end
diff --git a/lib/algora_web/live/job/create_live.ex b/lib/algora_web/live/job/create_live.ex
deleted file mode 100644
index e7a999d15..000000000
--- a/lib/algora_web/live/job/create_live.ex
+++ /dev/null
@@ -1,363 +0,0 @@
-defmodule AlgoraWeb.Job.CreateLive do
- @moduledoc false
- use AlgoraWeb, :live_view
-
- alias Algora.Accounts
- alias Algora.Accounts.User
-
- def mount(_params, session, socket) do
- job = %{
- country: socket.assigns.current_country,
- tech_stack: ["Elixir"],
- title: "",
- scope: %{size: nil, duration: nil, experience: nil},
- budget: %{type: :hourly, from: nil, to: nil},
- description: ""
- }
-
- {:ok,
- socket
- |> assign(step: 1)
- |> assign(total_steps: 5)
- |> assign(job: job)
- |> assign(current_user: %{email: session["user_email"]})
- |> assign(matching_devs: get_matching_devs(job))}
- end
-
- def render(assigns) do
- ~H"""
-
-
-
- {@step} / 5
-
Create Your Job
-
-
- {render_step(assigns)}
-
-
- <%= if @step > 1 do %>
-
- Previous
-
- <% else %>
-
- <% end %>
- <%= if @step < @total_steps do %>
-
- Next: {next_step_label(@step)}
-
- <% else %>
-
- Submit Job
-
- <% end %>
-
-
-
-
- Matching Developers
-
- <%= if @matching_devs == [] do %>
-
Add tech_stack to see matching developers
- <% else %>
- <%= for dev <- @matching_devs do %>
-
-
-
-
-
-
-
{dev.name} {dev.flag}
-
@{User.handle(dev)}
-
-
-
Earned
-
- {Money.to_string!(dev.total_earned)}
-
-
-
-
-
-
- <%= for tech <- dev.tech_stack do %>
-
- {tech}
-
- <% end %>
-
-
-
-
-
- <% end %>
- <% end %>
-
-
- """
- end
-
- def render_step(%{step: 1} = assigns) do
- ~H"""
-
-
- Which specific tech stack do you need your Elixir developer to have?
-
-
-
-
-
-
-
- <%= for tech <- ["Elixir", "Phoenix", "Phoenix LiveView", "PostgreSQL"] do %>
-
- {tech}
-
- ×
-
-
- <% end %>
-
-
-
-
- Popular tech stacks for Software Developers
-
-
- <%= for tech <- ["JavaScript", "CSS", "PHP", "React", "HTML", "Node.js", "iOS", "MySQL", "Python", "HTML5"] do %>
-
- + {tech}
-
- <% end %>
-
-
-
- """
- end
-
- def render_step(%{step: 2} = assigns) do
- ~H"""
-
- """
- end
-
- def render_step(%{step: 3} = assigns) do
- ~H"""
-
-
Define the job scope
-
-
- Job size
-
- Select size
- Small
- Medium
- Large
-
-
-
- Job duration
-
- Select duration
- Short term
- Medium term
- Long term
-
-
-
- Experience level
-
- Select experience level
- Entry
-
- Intermediate
-
- Expert
-
-
-
-
- """
- end
-
- def render_step(%{step: 4} = assigns) do
- ~H"""
-
-
Set your budget
-
-
- Budget type
-
- Hourly rate
- Fixed price
-
-
-
-
-
- """
- end
-
- def render_step(%{step: 5} = assigns) do
- ~H"""
-
-
Describe your job
-
-
-
-
- """
- end
-
- defp update_job_field(job, "tech_stack", _value, %{"tech" => tech}) do
- tech_stack =
- if tech in job.tech_stack,
- do: List.delete(job.tech_stack, tech),
- else: [tech | job.tech_stack]
-
- %{job | tech_stack: tech_stack}
- end
-
- defp update_job_field(job, "scope." <> scope_field, value, _params) do
- scope = Map.put(job.scope, String.to_atom(scope_field), value)
- %{job | scope: scope}
- end
-
- defp update_job_field(job, "budget." <> budget_field, value, _params) do
- budget = Map.put(job.budget, String.to_atom(budget_field), value)
- %{job | budget: budget}
- end
-
- defp update_job_field(job, field, value, _params) do
- Map.put(job, 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)}
- end
-
- def handle_event("submit", _, socket) do
- # Handle job submission
- {:noreply, socket}
- end
-
- def handle_event("add_tech", %{"tech" => tech}, socket) do
- updated_tech_stack = Enum.uniq([tech | socket.assigns.job.tech_stack])
- updated_job = Map.put(socket.assigns.job, :tech_stack, updated_tech_stack)
- {:noreply, assign(socket, job: updated_job)}
- end
-
- def handle_event("remove_tech", %{"tech" => tech}, socket) do
- updated_tech_stack = List.delete(socket.assigns.job.tech_stack, tech)
- updated_job = Map.put(socket.assigns.job, :tech_stack, updated_tech_stack)
- {:noreply, assign(socket, job: updated_job)}
- end
-
- def handle_event("update_job", %{"field" => field, "value" => value} = params, socket) do
- updated_job = update_job_field(socket.assigns.job, field, value, params)
- matching_devs = get_matching_devs(updated_job)
- {:noreply, assign(socket, job: updated_job, matching_devs: matching_devs)}
- end
-
- defp next_step_label(1), do: "Job Name"
- defp next_step_label(2), do: "Scope"
- defp next_step_label(3), do: "Budget"
- defp next_step_label(4), do: "Description"
-
- defp get_matching_devs(job) do
- Accounts.list_developers(
- limit: 5,
- sort_by_country: job.country,
- sort_by_tech_stack: job.tech_stack,
- earnings_gt: Money.new!(200, "USD")
- )
- end
-end
diff --git a/lib/algora_web/live/job/index_live.ex b/lib/algora_web/live/job/index_live.ex
deleted file mode 100644
index 23eb7c206..000000000
--- a/lib/algora_web/live/job/index_live.ex
+++ /dev/null
@@ -1,182 +0,0 @@
-defmodule AlgoraWeb.Job.IndexLive do
- @moduledoc false
- use AlgoraWeb, :live_view
-
- def mount(_params, _session, socket) do
- jobs = [
- %{
- id: 1,
- title: "Senior Elixir Developer",
- country: "US",
- tech_stack: ["Elixir", "Phoenix", "Phoenix LiveView", "PostgreSQL"],
- scope: %{size: "medium", duration: "medium", experience: "intermediate"},
- budget: %{type: :hourly, from: Money.new!(50, :USD), to: Money.new!(75, :USD)},
- description: "Looking for an experienced developer to join our team...",
- posted_at: ~N[2024-03-15 10:00:00],
- client: %{
- name: "John Doe",
- avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=John",
- jobs_posted: 5
- }
- },
- %{
- id: 2,
- title: "Full Stack Developer",
- country: "UK",
- tech_stack: ["Elixir", "Phoenix", "PostgreSQL", "JavaScript"],
- scope: %{size: "large", duration: "long", experience: "expert"},
- budget: %{type: :fixed, from: Money.new!(15_000, :USD), to: Money.new!(20_000, :USD)},
- description: "Need a full stack developer to join our growing team...",
- posted_at: ~N[2024-03-14 15:30:00],
- client: %{
- name: "Sarah Smith",
- avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=Sarah",
- jobs_posted: 3
- }
- }
- ]
-
- jobs =
- Enum.map(jobs, fn job ->
- Map.put(job, :applicants, [
- %{
- name: "Alice Johnson",
- designation: "Senior Developer",
- image: "https://api.dicebear.com/7.x/avataaars/svg?seed=Alice"
- },
- %{
- name: "Bob Smith",
- designation: "Full Stack Engineer",
- image: "https://api.dicebear.com/7.x/avataaars/svg?seed=Bob"
- },
- %{
- name: "Charlie Davis",
- designation: "Senior Developer",
- image: "https://api.dicebear.com/7.x/avataaars/svg?seed=Charlie"
- },
- %{
- name: "Dave Johnson",
- designation: "Full Stack Engineer",
- image: "https://api.dicebear.com/7.x/avataaars/svg?seed=Dave"
- }
- ])
- end)
-
- {:ok, assign(socket, jobs: jobs)}
- end
-
- def render(assigns) do
- ~H"""
-
-
-
Available Jobs
-
-
- <%= for job <- @jobs do %>
-
-
-
-
- <.link navigate={~p"/jobs/#{job.id}"} class="transition hover:text-indigo-400">
- {job.title}
-
-
-
-
- <.icon name="tabler-clock" class="h-4 w-4" />
- {Calendar.strftime(job.posted_at, "%B %d, %Y")}
-
-
- <.icon name="tabler-world" class="h-4 w-4" />
- {job.country}
-
-
-
-
-
- <%= case job.budget.type do %>
- <% :hourly -> %>
- ${job.budget.from}-{job.budget.to}/hour
- <% :fixed -> %>
- {Money.to_string!(job.budget.from)}-{Money.to_string!(job.budget.to)}
- <% end %>
-
-
- {String.capitalize("#{job.budget.type}")} Rate
-
-
-
-
-
- {job.description}
-
-
-
- <%= for tech <- job.tech_stack do %>
-
- {tech}
-
- <% end %>
-
-
-
-
-
-
-
{job.client.name}
-
- {job.client.jobs_posted} jobs posted
-
-
-
-
-
-
- <%= for applicant <- job.applicants do %>
-
-
-
-
-
-
-
- {applicant.name}
-
-
{applicant.designation}
-
-
-
- <% end %>
-
-
-
-
- <.icon name="tabler-chart-bar" class="h-4 w-4" />
- {String.capitalize(job.scope.experience)}
-
-
- <.icon name="tabler-clock" class="h-4 w-4" />
- {String.capitalize(job.scope.duration)} term
-
-
- <.icon name="tabler-layout-grid" class="h-4 w-4" />
- {String.capitalize(job.scope.size)} size
-
-
-
-
-
- <% end %>
-
-
-
- """
- end
-end
diff --git a/lib/algora_web/live/job/view_live.ex b/lib/algora_web/live/job/view_live.ex
deleted file mode 100644
index 893800b09..000000000
--- a/lib/algora_web/live/job/view_live.ex
+++ /dev/null
@@ -1,178 +0,0 @@
-defmodule AlgoraWeb.Job.ViewLive do
- @moduledoc false
- use AlgoraWeb, :live_view
-
- def mount(%{"id" => id}, _session, socket) do
- # Mock data for a single job (in production, you'd fetch this from your database)
- job = %{
- id: id,
- title: "Senior Elixir Developer",
- country: socket.assigns.current_country,
- tech_stack: ["Elixir", "Phoenix", "Phoenix LiveView", "PostgreSQL"],
- scope: %{size: "medium", duration: "medium", experience: "intermediate"},
- budget: %{type: :hourly, from: 50, to: 75},
- description: """
- Looking for an experienced developer to join our team. The role includes:
-
- • User authentication and authorization
- • Real-time message delivery
- • Message history and search
- • File sharing capabilities
- • Read receipts and typing indicators
- • Group chat functionality
-
- The ideal candidate should have strong experience with Elixir/Phoenix and WebSocket implementations.
- """,
- posted_at: ~N[2024-03-15 10:00:00],
- client: %{
- name: "John Doe",
- avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=John",
- jobs_posted: 5,
- member_since: ~D[2023-01-15],
- last_active: ~N[2024-03-20 15:30:00]
- }
- }
-
- {:ok, assign(socket, job: job)}
- end
-
- def render(assigns) do
- ~H"""
-
-
-
- <.link navigate={~p"/jobs"} class="transition hover:text-white">
- <.icon name="tabler-arrow-left" class="h-5 w-5" />
-
-
Back to jobs
-
-
-
- <%!-- Header Section --%>
-
-
-
-
- {@job.title}
-
-
-
- <.icon name="tabler-clock" class="h-4 w-4" />
- Posted {Calendar.strftime(@job.posted_at, "%B %d, %Y")}
-
-
- <.icon name="tabler-world" class="h-4 w-4" />
- {@job.country}
-
-
-
-
-
- <%= case @job.budget.type do %>
- <% :hourly -> %>
- ${@job.budget.from}-{@job.budget.to}/hour
- <% :fixed -> %>
- {Money.to_string!(@job.budget.from)}-{Money.to_string!(@job.budget.to)}
- <% end %>
-
-
- {String.capitalize("#{@job.budget.type}")} Rate
-
-
-
-
-
-
- <%!-- Main Content --%>
-
- <%!-- Job Details --%>
-
-
Job Details
-
-
- {@job.description}
-
-
-
-
- <%!-- Tech Stack Required --%>
-
-
Tech Stack Required
-
- <%= for tech <- @job.tech_stack do %>
-
- {tech}
-
- <% end %>
-
-
-
-
- <%!-- Sidebar --%>
-
- <%!-- Apply Button --%>
-
- Apply Now
-
-
- <%!-- Job Scope --%>
-
-
Job Scope
-
-
-
Experience Level
-
- <.icon name="tabler-chart-bar" class="h-4 w-4" />
- {String.capitalize(@job.scope.experience)}
-
-
-
-
Job Length
-
- <.icon name="tabler-clock" class="h-4 w-4" />
- {String.capitalize(@job.scope.duration)} term
-
-
-
-
Job Size
-
- <.icon name="tabler-layout-grid" class="h-4 w-4" />
- {String.capitalize(@job.scope.size)} size
-
-
-
-
-
- <%!-- Client Info --%>
-
-
About the Client
-
-
-
-
{@job.client.name}
-
- {@job.client.jobs_posted} jobs posted
-
-
-
-
-
-
Member Since
-
{Calendar.strftime(@job.client.member_since, "%B %Y")}
-
-
-
Last Active
-
- {Calendar.strftime(@job.client.last_active, "%B %d, %Y")}
-
-
-
-
-
-
-
-
-
- """
- end
-end
diff --git a/lib/algora_web/live/org/bounty_hook.ex b/lib/algora_web/live/org/bounty_hook.ex
deleted file mode 100644
index a9a9cedeb..000000000
--- a/lib/algora_web/live/org/bounty_hook.ex
+++ /dev/null
@@ -1,37 +0,0 @@
-defmodule AlgoraWeb.Org.BountyHook do
- @moduledoc false
- use Phoenix.Component
-
- import Ecto.Changeset
- import Phoenix.LiveView
-
- alias Algora.Bounties
- alias Algora.Parser
-
- def on_mount(:default, _params, _session, socket) do
- {:cont, attach_hook(socket, :handle_create_bounty, :handle_event, &handle_create_bounty/3)}
- end
-
- defp handle_create_bounty("create_bounty", %{"github_issue_url" => url, "amount" => amount}, socket) do
- %{current_user: creator, current_org: owner} = socket.assigns
-
- # TODO: use AlgoraWeb.Community.DashboardLive.BountyForm
- with {:ok, [ticket_ref: ticket_ref], _, _, _, _} <- Parser.full_ticket_ref(url),
- {:ok, _bounty} <-
- Bounties.create_bounty(%{creator: creator, owner: owner, ticket_ref: ticket_ref, amount: amount}) do
- {:halt,
- socket
- |> put_flash(:info, "Bounty created successfully")
- |> push_navigate(to: "/org/#{owner.handle}/bounties")}
- else
- {:error, :already_exists} ->
- {:halt, put_flash(socket, :warning, "You have already created a bounty for this ticket")}
-
- {:error, _reason} ->
- changeset = add_error(socket.assigns.new_bounty_form.changeset, :github_issue_url, "Invalid URL")
- {:halt, assign(socket, :new_bounty_form, to_form(changeset))}
- end
- end
-
- defp handle_create_bounty(_event, _params, socket), do: {:cont, socket}
-end
diff --git a/lib/algora_web/live/org/create_bounty_live.ex b/lib/algora_web/live/org/create_bounty_live.ex
deleted file mode 100644
index 6a744503c..000000000
--- a/lib/algora_web/live/org/create_bounty_live.ex
+++ /dev/null
@@ -1,522 +0,0 @@
-defmodule AlgoraWeb.Org.CreateBountyLive do
- @moduledoc false
- use AlgoraWeb, :live_view
-
- import Ecto.Changeset
-
- alias Algora.Accounts
- alias Algora.Accounts.User
- alias Algora.Bounties
- alias Algora.Parser
- alias AlgoraWeb.Org.Forms.BountyForm
-
- def mount(_params, _session, socket) do
- recent_bounties = Bounties.list_bounties(limit: 10)
- matching_devs = Accounts.list_developers(org_id: socket.assigns.current_org.id, limit: 5)
-
- changeset = BountyForm.changeset(%BountyForm{}, %{})
-
- {:ok,
- socket
- |> assign(:recent_bounties, recent_bounties)
- |> assign(:matching_devs, matching_devs)
- |> assign(:selected_dev, nil)
- |> assign(:show_dev_drawer, false)
- |> assign_form(changeset)}
- end
-
- def render(assigns) do
- ~H"""
-
-
-
-
-
Create New Bounty
- (looking for ideas?)
-
- <.button type="submit" phx-disable-with="Creating..." size="sm">
- Create bounty
-
-
- <.simple_form
- for={@form}
- phx-change="validate"
- phx-submit="create_bounty"
- class="mt-6 space-y-6"
- >
-
-
- <.label for="title" class="mb-2 text-sm font-medium">
- Title
-
- <.input
- type="text"
- field={@form[:title]}
- placeholder="Brief description of the task"
- required
- class="w-full rounded-lg border-input bg-background"
- />
-
-
- <.label for="ticket_url" class="mb-2 text-sm font-medium">
- Ticket
-
- (GitHub, Linear, Figma, Jira, Google Docs...)
-
-
-
-
- <.icon
- name={get_url_icon(@form[:ticket_url].value)}
- class="h-5 w-5 text-muted-foreground"
- />
-
- <.input
- type="url"
- field={@form[:ticket_url]}
- placeholder="https://github.com/owner/repo/issues/123"
- required
- class="w-full rounded-lg border-input bg-background pl-10"
- />
-
-
-
-
-
- Share Bounty With
-
-
-
-
-
- Private Share
-
-
-
-
-
- Share with specific people via email or link
-
-
-
-
- <.input
- type="text"
- field={@form[:share_emails]}
- placeholder="email1@example.com, email2@example.com"
- class="w-full rounded-lg border-input bg-background"
- />
-
- <.icon name="tabler-mail" class="h-5 w-5 text-muted-foreground" />
-
-
-
- <.input
- type="text"
- field={@form[:share_url]}
- value="https://algora.io/bounties/share/abc123"
- readonly
- class="w-full cursor-pointer rounded-lg border-input bg-background/50"
- />
-
-
- <.icon name="tabler-copy" class="h-5 w-5" />
-
-
-
-
-
-
-
-
-
-
-
- Platform
-
-
-
-
-
- Share with all platform users
-
-
-
-
-
-
-
-
-
-
-
-
Applicants
-
- Developers interested in freelancing with you
-
-
-
-
-
-
-
-
-
- Developer
-
-
- Tech stack
-
-
- Actions
-
-
-
-
- <%= for dev <- @matching_devs do %>
-
-
-
-
-
-
-
-
- {dev.name}
- <%= if dev.flag do %>
- {dev.flag}
- <% end %>
-
-
@{dev.handle}
-
-
-
-
-
-
- <%= for tech <- Enum.take(dev.tech_stack, 3) do %>
-
- {tech}
-
- <% end %>
- <%= if length(dev.tech_stack) > 3 do %>
-
- +{length(dev.tech_stack) - 3} more
-
- <% end %>
-
-
-
- <.icon name="tabler-diamond" class="h-4 w-4" />
- {dev.bounties} bounties
-
-
- <.icon name="tabler-cash" class="h-4 w-4" />
- {Money.to_string!(dev.total_earned)}
-
-
-
-
-
- <.button phx-click="view_dev" phx-value-id={dev.id} size="sm" variant="default">
- View Profile
-
-
-
- <% end %>
-
-
-
-
-
-
- <.bounties_card current_org={@current_org} bounties={@recent_bounties} />
-
-
- <.drawer show={@show_dev_drawer} on_cancel="close_drawer">
- <%= if @selected_dev do %>
- <.drawer_header class="flex items-center gap-4">
-
-
-
- {@selected_dev.name} {@selected_dev.flag}
-
-
- @{User.handle(@selected_dev)}
-
-
- <%= for tech <- @selected_dev.tech_stack do %>
-
- {tech}
-
- <% end %>
-
-
-
- <.drawer_content>
-
-
-
-
-
-
Stats
-
-
-
-
- {Money.to_string!(@selected_dev.total_earned)}
-
-
-
Total Earnings
-
-
-
-
- {@selected_dev.bounties}
-
-
-
Bounties Solved
-
-
-
-
- {@selected_dev.projects}
-
-
-
Projects Contributed
-
-
-
-
-
-
-
-
- <.icon name="tabler-message" class="h-4 w-4" />
-
- {User.handle(@selected_dev)} wrote to you {Algora.Util.time_ago(
- DateTime.utc_now()
- |> DateTime.add(-3, :day)
- )}
-
-
-
-
- {@selected_dev.message}
-
-
-
-
-
-
-
Past Reviews
-
- <%= for review <- [
- %{stars: 5, comment: "Exceptional problem-solving tech_stack and great communication throughout the project.", company: "TechCorp Inc."},
- %{stars: 4, comment: "Delivered high-quality work ahead of schedule. Would definitely work with again.", company: "StartupXYZ"},
- %{stars: 5, comment: "Outstanding technical expertise and professional attitude.", company: "DevLabs"}
- ] do %>
-
-
- <%= for i <- 1..5 do %>
- <.icon
- name="tabler-star-filled"
- class={"#{if i <= review.stars, do: "text-foreground", else: "text-muted-foreground/25"} h-4 w-4"}
- />
- <% end %>
-
-
{review.comment}
-
— {review.company}
-
- <% end %>
-
-
-
-
- <.drawer_footer>
-
- <.button phx-click="close_drawer" variant="hover:destructive" size="lg">
- Decline
-
- <.button phx-click="accept_dev" phx-value-id={@selected_dev.id} class="flex-1" size="lg">
- Accept
-
-
-
- <% end %>
-
-
- """
- end
-
- def bounties_card(assigns) do
- ~H"""
-
-
-
-
Bounties
-
Most recently posted bounties
-
-
- <.link
- class="whitespace-pre text-sm text-muted-foreground hover:underline hover:brightness-125"
- href={"/org/#{@current_org.handle}/bounties?status=open"}
- >
- View all
-
-
-
-
-
- <%= for bounty <- @bounties do %>
-
- <.link
- class="group relative flex flex-col items-start gap-x-4 gap-y-2 py-4 sm:flex-row sm:items-center"
- rel="noopener"
- href={"https://github.com/#{bounty.ticket.owner}/#{bounty.ticket.repo}/issues/#{bounty.ticket.number}"}
- >
-
-
-
-
- {bounty.ticket.title}
-
-
-
-
- tv#{bounty.ticket.number}
-
-
-
-
-
- {Algora.Util.time_ago(bounty.inserted_at)}
-
-
-
-
-
-
-
-
- <% end %>
-
-
-
- """
- end
-
- def handle_event("validate", %{"bounty_form" => params}, socket) do
- changeset = BountyForm.changeset(%BountyForm{}, params)
-
- {:noreply, assign_form(socket, changeset)}
- end
-
- def handle_event("create_bounty", %{"bounty_form" => params}, socket) do
- %{current_user: creator, current_org: owner} = socket.assigns
-
- # TODO: use AlgoraWeb.Community.DashboardLive.BountyForm
- changeset =
- %BountyForm{}
- |> BountyForm.changeset(params)
- |> Map.put(:action, :validate)
-
- if changeset.valid? do
- amount = get_change(changeset, :amount)
- url = get_change(changeset, :ticket_url)
-
- with {:ok, [ticket_ref: ticket_ref], _, _, _, _} <- Parser.full_ticket_ref(url),
- {:ok, _bounty} <-
- Bounties.create_bounty(%{creator: creator, owner: owner, ticket_ref: ticket_ref, amount: amount}) do
- {:noreply,
- socket
- |> put_flash(:info, "Bounty created successfully")
- |> push_navigate(to: "/org/#{owner.handle}/bounties")}
- else
- {:error, :already_exists} ->
- {:noreply, put_flash(socket, :warning, "You have already created a bounty for this ticket")}
-
- {:error, _reason} ->
- changeset = add_error(socket.assigns.new_bounty_form.changeset, :github_issue_url, "Invalid URL")
- {:noreply, assign(socket, :new_bounty_form, to_form(changeset))}
- end
- else
- {:noreply,
- socket
- |> assign_form(changeset)
- |> put_flash(:error, "Please fix the errors in the form")}
- end
- end
-
- def handle_event("view_dev", %{"id" => dev_id}, socket) do
- {:ok, dev} = Accounts.fetch_developer(dev_id)
-
- {:noreply,
- socket
- |> assign(:selected_dev, dev)
- |> assign(:show_dev_drawer, true)}
- end
-
- def handle_event("close_drawer", _, socket) do
- {:noreply, assign(socket, :show_dev_drawer, false)}
- end
-
- def handle_event("accept_dev", %{"id" => dev_id}, socket) do
- # Add logic to accept developer
- {:noreply,
- socket
- |> put_flash(:info, "Developer accepted successfully")
- |> assign(:show_dev_drawer, false)
- |> assign(:selected_dev, nil)
- |> assign(:matching_devs, Enum.reject(socket.assigns.matching_devs, &(&1.id == dev_id)))}
- end
-
- defp assign_form(socket, %Ecto.Changeset{} = changeset) do
- assign(socket, :form, to_form(changeset))
- end
-
- defp get_url_icon(nil), do: "tabler-link"
-
- defp get_url_icon(url) when is_binary(url) do
- cond do
- String.contains?(url, "github.com") -> "tabler-brand-github"
- String.contains?(url, "figma.com") -> "tabler-brand-figma"
- String.contains?(url, "docs.google.com") -> "tabler-brand-google"
- # String.contains?(url, "linear.app") -> "tabler-brand-linear"
- # String.contains?(url, "atlassian.net") -> "tabler-brand-jira"
- true -> "tabler-link"
- end
- end
-end
diff --git a/lib/algora_web/live/org/create_job_live.ex b/lib/algora_web/live/org/create_job_live.ex
deleted file mode 100644
index dd5e1a0ff..000000000
--- a/lib/algora_web/live/org/create_job_live.ex
+++ /dev/null
@@ -1,728 +0,0 @@
-defmodule AlgoraWeb.Org.CreateJobLive do
- @moduledoc false
- use AlgoraWeb, :live_view
-
- alias Algora.Accounts
- alias Algora.Accounts.User
- alias Algora.Bounties
- alias Algora.Jobs
- alias AlgoraWeb.Org.Forms.JobForm
-
- def mount(_params, _session, socket) do
- form =
- %JobForm{}
- |> JobForm.changeset(%{
- work_type: "remote",
- projects: [%{"title" => "", "url" => "", "amount" => ""}]
- })
- |> to_form(as: :job_form)
-
- {:ok,
- socket
- |> assign(:recent_bounties, Bounties.list_bounties(limit: 10))
- |> assign(
- :matching_devs,
- Accounts.list_developers(org_id: socket.assigns.current_org.id, limit: 5)
- )
- |> assign(:selected_dev, nil)
- |> assign(:show_dev_drawer, false)
- |> assign(:is_published, false)
- |> assign(:show_publish_job_drawer, false)
- |> assign(:form, form)}
- end
-
- def render(assigns) do
- ~H"""
-
-
-
-
-
Create New Job
- <.tooltip>
- <.tooltip_trigger>
- <.icon name="tabler-help" class="h-5 w-5 text-muted-foreground" />
-
- <.tooltip_content side="bottom" class="w-80 space-y-2 p-3">
-
Example Jobs:
-
-
-
Senior Backend Engineer
-
- Lead development of our core API infrastructure and microservices
-
-
-
-
Frontend Developer
-
- Build responsive web applications using React and TypeScript
-
-
-
-
DevOps Engineer
-
- Manage cloud infrastructure and CI/CD pipelines
-
-
-
-
Full Stack Developer
-
- Work across the entire stack to deliver end-to-end features
-
-
-
-
-
-
- <.button type="submit" phx-disable-with="Creating..." size="sm">
- Create job
-
-
- <.simple_form for={@form} phx-change="validate" phx-submit="create_job" class="space-y-6 p-6">
-
-
- <.label for="title" class="mb-2 text-sm font-medium">
- Title
-
- <.input
- type="text"
- field={@form[:title]}
- placeholder="Brief description of the task"
- required
- class="w-full rounded-lg border-input bg-background"
- />
-
-
- Annual Compensation Range
-
-
-
- <.input
- placeholder="From"
- field={@form[:min_compensation]}
- min="0"
- class="rounded-none border-none"
- />
-
- USD
-
-
-
-
-
- <.input
- placeholder="To"
- field={@form[:max_compensation]}
- min="0"
- class="rounded-none border-none"
- />
-
- USD
-
-
-
-
-
-
-
-
- Work Type
-
-
- <.input
- type="radio"
- name="job_form[work_type]"
- field={@form[:work_type]}
- value="remote"
- checked={@form[:work_type].value == "remote"}
- class="sr-only"
- />
-
-
- <.icon name="tabler-world" class="h-5 w-5" />
- Remote
-
- Work from anywhere
-
-
-
-
- <.input
- type="radio"
- name="job_form[work_type]"
- field={@form[:work_type]}
- value="in_person"
- checked={@form[:work_type].value == "in_person"}
- class="sr-only"
- />
-
-
- <.icon name="tabler-building" class="h-5 w-5" />
- In-Person
-
- Office-based work
-
-
-
-
-
-
-
-
-
Add Contract-to-Hire
-
- Interview your top applicants with a paid project. It's the best way to evaluate fit, accelerate onboarding and get work done while hiring.
-
-
-
-
- <%= for {project, i} <- Enum.with_index(@form.params["projects"] || []) do %>
-
-
-
- <.label>Project Title
- <.input
- field={project[:title]}
- name="projects[#{i}][title]"
- value={project[:title]}
- type="text"
- />
-
-
- <.label for="ticket_url" class="mb-2 text-sm font-medium">
- Ticket
-
- (GitHub, Linear, Figma, Jira, Google Docs...)
-
-
-
-
- <.icon
- name={get_url_icon(@form[:ticket_url].value)}
- class="h-5 w-5 text-muted-foreground"
- />
-
- <.input
- type="url"
- field={@form[:ticket_url]}
- placeholder="https://github.com/owner/repo/issues/123"
- required
- class="w-full rounded-lg border-input bg-background pl-10"
- />
-
-
-
- <.label>Payment Amount (USD)
-
- <.input
- field={project[:amount]}
- name="projects[#{i}][amount]"
- value={project[:amount]}
- />
-
- USD
-
-
-
-
- <.button
- type="button"
- variant="ghost"
- size="sm"
- phx-click="remove_project"
- phx-value-index={i}
- >
- <.icon name="tabler-trash" class="h-4 w-4 text-destructive" />
-
-
- <% end %>
-
-
- <.button type="button" variant="outline" size="sm" phx-click="add_project" class="mx-auto">
- <.icon name="tabler-plus" class="mr-2 h-4 w-4" /> Add Project
-
-
-
-
-
-
-
-
Applicants
-
- Developers interested in working full-time with you
-
-
-
-
-
-
-
-
-
- Developer
-
-
- Tech stack
-
-
- Actions
-
-
-
-
- <%= for dev <- @matching_devs do %>
-
-
-
-
-
-
-
-
- {dev.name}
- <%= if dev.flag do %>
- {dev.flag}
- <% end %>
-
-
@{dev.handle}
-
-
-
-
-
-
- <%= for tech <- Enum.take(dev.tech_stack, 3) do %>
-
- {tech}
-
- <% end %>
- <%= if length(dev.tech_stack) > 3 do %>
-
- +{length(dev.tech_stack) - 3} more
-
- <% end %>
-
-
-
- <.icon name="tabler-diamond" class="h-4 w-4" />
- {dev.bounties} bounties
-
-
- <.icon name="tabler-cash" class="h-4 w-4" />
- {Money.to_string!(dev.total_earned)}
-
-
-
-
-
- <.button
- phx-click="view_dev"
- phx-value-id={dev.id}
- size="sm"
- variant={if @is_published, do: "default", else: "outline"}
- >
- View Profile
-
-
-
- <% end %>
-
-
- <%= if !@is_published do %>
-
-
-
- <.button type="button" variant="default" size="xl" phx-click="publish_job">
- Publish Job
-
-
-
- <% end %>
-
-
-
-
- <.bounties_card current_org={@current_org} bounties={@recent_bounties} />
-
-
- <.drawer show={@show_dev_drawer} on_cancel="close_drawer">
- <%= if @selected_dev do %>
- <.drawer_header class="flex items-center gap-4">
-
-
-
- {@selected_dev.name} {@selected_dev.flag}
-
-
- @{@selected_dev.handle}
-
-
- <%= for tech <- @selected_dev.tech_stack do %>
-
- {tech}
-
- <% end %>
-
-
-
- <.drawer_content>
-
-
-
-
-
-
Stats
-
-
-
-
- {Money.to_string!(@selected_dev.total_earned)}
-
-
-
Total Earnings
-
-
-
-
- {@selected_dev.bounties}
-
-
-
Bounties Solved
-
-
-
-
- {@selected_dev.projects}
-
-
-
Projects Contributed
-
-
-
-
-
-
-
-
- <.icon name="tabler-message" class="h-4 w-4" />
-
- {User.handle(@selected_dev)} wrote to you {Algora.Util.time_ago(
- DateTime.utc_now()
- |> DateTime.add(-3, :day)
- )}
-
-
-
-
- {@selected_dev.message}
-
-
-
-
-
-
-
Past Reviews
-
- <%= for review <- [
- %{stars: 5, comment: "Exceptional problem-solving tech_stack and great communication throughout the project.", company: "TechCorp Inc."},
- %{stars: 4, comment: "Delivered high-quality work ahead of schedule. Would definitely work with again.", company: "StartupXYZ"},
- %{stars: 5, comment: "Outstanding technical expertise and professional attitude.", company: "DevLabs"}
- ] do %>
-
-
- <%= for i <- 1..5 do %>
- <.icon
- name="tabler-star-filled"
- class={"#{if i <= review.stars, do: "text-foreground", else: "text-muted-foreground/25"} h-4 w-4"}
- />
- <% end %>
-
-
{review.comment}
-
— {review.company}
-
- <% end %>
-
-
-
-
- <.drawer_footer>
-
- <.button phx-click="close_drawer" variant="hover:destructive" size="lg">
- Decline
-
- <.button phx-click="accept_dev" phx-value-id={@selected_dev.id} class="flex-1" size="lg">
- Accept
-
-
-
- <% end %>
-
-
- <.drawer id="publish-job-drawer" show={@show_publish_job_drawer} on_cancel="close_drawer">
- <.drawer_header>
- <.drawer_title>Publish Job
-
- <.drawer_content class="space-y-6">
-
-
-
-
- <.card>
- <.card_header>
- <.card_title>Why Algora?
-
- <.card_content>
-
-
-
- <.icon name="tabler-users" class="h-5 w-5" />
-
-
- Reach Top Talent
-
- Publish to the Algora network of proven developers
-
-
-
-
-
-
-
- <.icon name="tabler-list-check" class="h-5 w-5" />
-
-
- Track Applicants
-
- Review and manage candidates in one place
-
-
-
-
-
-
-
- <.icon name="tabler-briefcase" class="h-5 w-5" />
-
-
- Contract-to-Hire
-
- Test fit with paid trial projects before hiring
-
-
-
-
-
-
-
- <.card>
- <.card_header>
- <.card_title>Payment Summary
-
- <.card_content>
-
-
-
- Job posting
-
-
- {Money.to_string!(Money.new!(599, :USD))}
-
-
-
-
-
Total Due
-
- {Money.to_string!(Money.new!(599, :USD))}
-
-
-
-
-
-
-
-
-
- <.button variant="outline" phx-click="close_drawer">
- Cancel
-
- <.button phx-click="submit_collaboration">
- <.icon name="tabler-credit-card" class="mr-2 h-4 w-4" /> Pay with Stripe
-
-
-
-
- """
- end
-
- def bounties_card(assigns) do
- ~H"""
-
-
-
-
Bounties
-
Most recently posted bounties
-
-
- <.link
- class="whitespace-pre text-sm text-muted-foreground hover:underline hover:brightness-125"
- href={"/org/#{@current_org.handle}/bounties?status=open"}
- >
- View all
-
-
-
-
-
- <%= for bounty <- @bounties do %>
-
- <.link
- class="group relative flex flex-col items-start gap-x-4 gap-y-2 py-4 sm:flex-row sm:items-center"
- rel="noopener"
- href={"https://github.com/#{bounty.ticket.owner}/#{bounty.ticket.repo}/issues/#{bounty.ticket.number}"}
- >
-
-
-
-
- {bounty.ticket.title}
-
-
-
-
- tv#{bounty.ticket.number}
-
-
-
-
-
- {Algora.Util.time_ago(bounty.inserted_at)}
-
-
-
-
-
-
-
-
- <% end %>
-
-
-
- """
- end
-
- def handle_event("validate", %{"job_form" => params}, socket) do
- form =
- %JobForm{}
- |> JobForm.changeset(params)
- |> Map.put(:action, :validate)
- |> to_form(as: :job_form)
-
- {:noreply, assign(socket, form: form)}
- end
-
- def handle_event("add_project", _, socket) do
- existing = Map.get(socket.assigns.form.params, "projects", [])
-
- params =
- Map.put(
- socket.assigns.form.params,
- "projects",
- existing ++ [%{"title" => "", "url" => "", "amount" => ""}]
- )
-
- form =
- %JobForm{}
- |> JobForm.changeset(params)
- |> to_form(as: :job_form)
-
- {:noreply, assign(socket, form: form)}
- end
-
- def handle_event("remove_project", %{"index" => index}, socket) do
- index = String.to_integer(index)
- existing = Map.get(socket.assigns.form.params, "projects", [])
-
- params =
- Map.put(
- socket.assigns.form.params,
- "projects",
- List.delete_at(existing, index)
- )
-
- form =
- %JobForm{}
- |> JobForm.changeset(params)
- |> to_form(as: :job_form)
-
- {:noreply, assign(socket, form: form)}
- end
-
- def handle_event("create_job", %{"job_form" => params}, socket) do
- case Jobs.create_job(params) do
- {:ok, job} ->
- {:noreply,
- socket
- |> put_flash(:info, "Job created successfully")
- |> redirect(to: ~p"/jobs/#{job}")}
-
- {:error, %Ecto.Changeset{} = changeset} ->
- {:noreply, assign(socket, form: to_form(changeset, as: :job_form))}
- end
- end
-
- def handle_event("publish_job", _, socket) do
- {:noreply, assign(socket, :show_publish_job_drawer, true)}
- end
-
- def handle_event("view_dev", %{"id" => dev_id}, socket) do
- {:ok, dev} = Accounts.fetch_developer(dev_id)
-
- {:noreply,
- socket
- |> assign(:selected_dev, dev)
- |> assign(:show_dev_drawer, true)}
- end
-
- def handle_event("close_drawer", _, socket) do
- {:noreply,
- socket
- |> assign(:show_dev_drawer, false)
- |> assign(:show_publish_job_drawer, false)}
- end
-
- def handle_event("accept_dev", %{"id" => dev_id}, socket) do
- # Add logic to accept developer
- {:noreply,
- socket
- |> put_flash(:info, "Developer accepted successfully")
- |> assign(:show_dev_drawer, false)
- |> assign(:selected_dev, nil)
- |> assign(:matching_devs, Enum.reject(socket.assigns.matching_devs, &(&1.id == dev_id)))}
- end
-
- def handle_event("begin_collaboration", _, socket) do
- {:noreply, assign(socket, :show_publish_job_drawer, true)}
- end
-
- def handle_event("submit_collaboration", _params, socket) do
- # TODO: Implement payment method addition and collaboration initiation
- {:noreply, socket}
- end
-
- defp get_url_icon(nil), do: "tabler-link"
-
- defp get_url_icon(url) when is_binary(url) do
- cond do
- String.contains?(url, "github.com") -> "tabler-brand-github"
- String.contains?(url, "figma.com") -> "tabler-brand-figma"
- String.contains?(url, "docs.google.com") -> "tabler-brand-google"
- # String.contains?(url, "linear.app") -> "tabler-brand-linear"
- # String.contains?(url, "atlassian.net") -> "tabler-brand-jira"
- true -> "tabler-link"
- end
- end
-end
diff --git a/lib/algora_web/live/org/create_live.ex b/lib/algora_web/live/org/create_live.ex
deleted file mode 100644
index d5a938748..000000000
--- a/lib/algora_web/live/org/create_live.ex
+++ /dev/null
@@ -1,141 +0,0 @@
-defmodule AlgoraWeb.Org.CreateLive do
- @moduledoc false
- use AlgoraWeb, :live_view
-
- alias Algora.Accounts
- alias Algora.Accounts.User
-
- def mount(_params, session, socket) do
- org = %{
- name: "",
- handle: "",
- email_domain: "",
- country: socket.assigns.current_country,
- tech_stack: ["Elixir"]
- }
-
- {:ok,
- socket
- |> assign(org: org)
- |> assign(current_user: %{email: session["user_email"]})
- |> assign(matching_orgs: Accounts.list_orgs(limit: 5))}
- end
-
- def render(assigns) do
- ~H"""
-
-
-
-
-
- Create Your Organization
-
-
-
-
-
-
-
- Create
-
-
-
-
-
- You're in good company
-
- <%= if @matching_orgs == [] do %>
-
Add tech_stack to see similar organizations
- <% else %>
- <%= for org <- @matching_orgs do %>
-
-
-
-
-
-
-
{org.name} {org.flag}
-
@{User.handle(org)}
-
-
-
Awarded
-
- {Money.to_string!(org.amount)}
-
-
-
-
-
-
- <%= for tech_stack <- org.tech_stack do %>
-
- {tech_stack}
-
- <% end %>
-
-
-
-
-
- <% end %>
- <% end %>
-
-
- """
- end
-
- def handle_event("submit", _, socket) do
- # Handle job submission
- {:noreply, socket}
- end
-
- def handle_event("update_org", %{"field" => field, "value" => value}, socket) do
- updated_org = Map.put(socket.assigns.org, String.to_atom(field), value)
- {:noreply, assign(socket, org: updated_org)}
- end
-end
diff --git a/lib/algora_web/live/org/dashboard_admin_live.ex b/lib/algora_web/live/org/dashboard_admin_live.ex
deleted file mode 100644
index 9d6ca2a3b..000000000
--- a/lib/algora_web/live/org/dashboard_admin_live.ex
+++ /dev/null
@@ -1,813 +0,0 @@
-defmodule AlgoraWeb.Org.DashboardAdminLive do
- @moduledoc false
- use AlgoraWeb, :live_view
-
- import AlgoraWeb.Components.Achievement
-
- alias Algora.Accounts
- alias Algora.Accounts.User
- alias Algora.Bounties
- alias Algora.Bounties.Bounty
- alias Algora.Contracts
- alias Algora.FeeTier
- alias Algora.Jobs
- alias Algora.Payments
- alias Algora.Reviews
- alias Algora.Util
- alias AlgoraWeb.Org.Forms.JobForm
-
- def mount(_params, _session, socket) do
- {:ok, contract} = Contracts.fetch_contract(client_id: socket.assigns.current_org.id, open?: true)
-
- %{tech_stack: tech_stack} = socket.assigns.current_org
-
- job_form =
- %JobForm{}
- |> JobForm.changeset(%{work_type: "remote"})
- |> to_form(as: :job_form)
-
- hourly_rate_mid =
- contract.hourly_rate_min
- |> Money.add!(contract.hourly_rate_max)
- |> Money.div!(2)
-
- weekly_amount_mid = Money.mult!(hourly_rate_mid, contract.hours_per_week)
-
- platform_fee_pct = hd(FeeTier.all()).fee
- transaction_fee_pct = Payments.get_transaction_fee_pct()
- total_fee_pct = Decimal.add(platform_fee_pct, transaction_fee_pct)
-
- {:ok,
- socket
- |> assign(:tech_stack, tech_stack)
- |> assign(:view_mode, "compact")
- |> assign(:looking_to_collaborate, true)
- |> assign(:hourly_rate_mid, hourly_rate_mid)
- |> assign(:weekly_amount_mid, weekly_amount_mid)
- |> assign(:platform_fee_pct, platform_fee_pct)
- |> assign(:transaction_fee_pct, transaction_fee_pct)
- |> assign(:total_fee_pct, total_fee_pct)
- |> assign(:selected_dev, nil)
- |> assign(:matching_devs, fetch_matching_devs(tech_stack))
- |> assign(:contract, contract)
- |> assign(:achievements, fetch_achievements(socket))
- |> assign(:show_begin_collaboration_drawer, false)
- |> assign(:job_form, job_form)}
- end
-
- def render(assigns) do
- ~H"""
-
-
-
-
-
-
- My matches
-
-
- Based on tech stack and hourly rate
-
-
- <.button phx-click="begin_collaboration" size="lg">
- Start collaborating
-
-
-
-
-
-
- <.contract contract={@contract} />
- <%= for user <- @matching_devs do %>
- <.matching_dev user={user} />
- <% end %>
-
-
-
-
-
-
-
-
-
-
-
Create New Job
- <.tooltip>
- <.tooltip_trigger>
- <.icon name="tabler-help" class="h-5 w-5 text-muted-foreground" />
-
- <.tooltip_content side="bottom" class="w-80 space-y-2 p-3">
-
Example Jobs:
-
-
-
Senior Backend Engineer
-
- Lead development of our core API infrastructure and microservices
-
-
-
-
Frontend Developer
-
- Build responsive web applications using React and TypeScript
-
-
-
-
-
-
- <.button type="submit" phx-disable-with="Creating..." size="sm">
- Create job
-
-
- <.simple_form
- for={@job_form}
- phx-change="validate_job"
- phx-submit="create_job"
- class="space-y-6 p-6"
- >
-
-
- <.label for="title" class="mb-2 text-sm font-medium">
- Title
-
- <.input
- type="text"
- field={@job_form[:title]}
- placeholder="Brief description of the task"
- required
- class="w-full rounded-lg border-input bg-background"
- />
-
-
- Annual Compensation Range
-
-
-
- <.input
- placeholder="From"
- field={@job_form[:min_compensation]}
- min="0"
- class="rounded-none border-none"
- />
-
- USD
-
-
-
-
-
- <.input
- placeholder="To"
- field={@job_form[:max_compensation]}
- min="0"
- class="rounded-none border-none"
- />
-
- USD
-
-
-
-
-
-
-
-
- Work Type
-
-
- <.input
- type="radio"
- name="job_form[work_type]"
- field={@job_form[:work_type]}
- value="remote"
- checked={@job_form[:work_type].value == "remote"}
- class="sr-only"
- />
-
-
- <.icon name="tabler-world" class="h-5 w-5" />
- Remote
-
- Work from anywhere
-
-
-
-
- <.input
- type="radio"
- name="job_form[work_type]"
- field={@job_form[:work_type]}
- value="in_person"
- checked={@job_form[:work_type].value == "in_person"}
- class="sr-only"
- />
-
-
- <.icon name="tabler-building" class="h-5 w-5" />
- In-Person
-
- Office-based work
-
-
-
-
-
-
-
-
-
-
- <.drawer
- id="begin-collaboration-drawer"
- show={@show_begin_collaboration_drawer}
- on_cancel="close_drawer"
- >
- <.drawer_header>
- <.drawer_title>Begin Collaboration
-
- <.drawer_content class="space-y-6">
-
-
-
-
-
-
-
-
-
- "The interview was that easy because I had 1 week as a contractor to knock out a project for them. If I didn't knock that out, then I wouldn't get the job. They didn't need to extend an offer. So contract-to-hire
- can
- actually be that easy."
-
-
- - Chris Griffing
-
-
-
-
-
-
- <.card>
- <.card_header>
- <.card_title>How it works
-
- <.card_content>
-
-
-
- <.icon name="tabler-credit-card" class="h-5 w-5" />
-
-
- Add payment method
-
- Add your credit card to initiate the collaboration
-
-
-
-
-
-
-
- <.icon name="tabler-lock" class="h-5 w-5" />
-
-
- Funds held in escrow
- <%= if @selected_dev do %>
-
- Once accepted,
-
- {Money.to_string!(@weekly_amount_mid)}
-
- will be charged and held securely
-
- <% else %>
-
- Once the developer accepts, the amount will be held securely
-
- <% end %>
-
-
-
-
-
-
- <.icon name="tabler-check" class="h-5 w-5" />
-
-
- Release and renew
-
- Release funds to the developer and continue collaboration
-
-
-
-
-
-
-
- <.card>
- <.card_header>
- <.card_title>Payment Summary
-
- <.card_content>
-
-
-
- Weekly amount ({@contract.hours_per_week} hours x {Money.to_string!(
- @hourly_rate_mid
- )}/hr)
-
-
- {Money.to_string!(@weekly_amount_mid)}
-
-
-
-
- Algora fees ({Util.format_pct(@platform_fee_pct)})
-
-
- {Money.to_string!(Money.mult!(@weekly_amount_mid, @platform_fee_pct))}
-
-
-
-
- Transaction fees ({Util.format_pct(@transaction_fee_pct)})
-
-
- {Money.to_string!(Money.mult!(@weekly_amount_mid, @transaction_fee_pct))}
-
-
-
-
-
Total Due
-
- {@weekly_amount_mid
- |> Money.mult!(Decimal.add(1, @total_fee_pct))
- |> Money.to_string!()}
-
-
-
-
-
Estimated based on the middle of your hourly rate range.
-
- Actual charges may vary (up to
-
- {@contract.hourly_rate_max
- |> Money.mult!(@contract.hours_per_week)
- |> Money.mult!(@total_fee_pct)
- |> Money.to_string!()}
-
- including fees).
-
-
-
-
-
-
-
-
- <.button variant="outline" phx-click="close_drawer">
- Cancel
-
- <.button phx-click="submit_collaboration">
- <.icon name="tabler-credit-card" class="mr-2 h-4 w-4" /> Add payment method
-
-
-
-
- """
- end
-
- defp fetch_achievements(socket) do
- achievements = [
- {&personalize_status/1, "Personalize Algora"},
- {&begin_collaboration_status/1, "Begin collaboration"},
- {&complete_first_contract_status/1, "Complete first contract"},
- {&unlock_lower_fees_status/1, "Unlock lower fees with a developer"},
- {&refer_a_friend/1, "Refer a friend"}
- ]
-
- {result, _} =
- Enum.reduce_while(achievements, {[], false}, fn {status_fn, name}, {acc, found_current} ->
- status = status_fn.(socket.assigns.current_org)
-
- result =
- cond do
- found_current -> {acc ++ [%{status: status, name: name}], found_current}
- status == :completed -> {acc ++ [%{status: status, name: name}], false}
- true -> {acc ++ [%{status: :current, name: name}], true}
- end
-
- {:cont, result}
- end)
-
- result
- end
-
- defp personalize_status(_socket), do: :completed
-
- defp begin_collaboration_status(org) do
- case Contracts.list_contracts(client_id: org.id, active_or_paid?: true, limit: 1) do
- [] -> :upcoming
- _ -> :completed
- end
- end
-
- defp complete_first_contract_status(org) do
- case Contracts.list_contracts(client_id: org.id, status: :paid, limit: 1) do
- [] -> :upcoming
- _ -> :completed
- end
- end
-
- defp unlock_lower_fees_status(org) do
- {_contractor_id, max_amount} = Payments.get_max_paid_to_single_contractor(org.id)
-
- case FeeTier.first_threshold_met?(max_amount) do
- false -> :upcoming
- _ -> :completed
- end
- end
-
- # TODO: implement referrals
- defp refer_a_friend(_socket), do: :upcoming
-
- def handle_event("handle_tech_input", %{"key" => "Enter", "value" => tech}, socket) when byte_size(tech) > 0 do
- tech_stack = Enum.uniq([String.trim(tech) | socket.assigns.tech_stack])
-
- {:noreply,
- socket
- |> assign(:tech_stack, tech_stack)
- |> assign(:bounties, Bounties.list_bounties(tech_stack: tech_stack, limit: 10))
- |> push_event("clear-input", %{selector: "[phx-keydown='handle_tech_input']"})}
- end
-
- def handle_event("handle_tech_input", _params, socket) do
- {:noreply, socket}
- end
-
- def handle_event("remove_tech", %{"tech" => tech}, socket) do
- tech_stack = List.delete(socket.assigns.tech_stack, tech)
-
- {:noreply,
- socket
- |> assign(:tech_stack, tech_stack)
- |> assign(:bounties, Bounties.list_bounties(tech_stack: tech_stack, limit: 10))}
- end
-
- def handle_event("view_mode", %{"value" => mode}, socket) do
- {:noreply, assign(socket, :view_mode, mode)}
- end
-
- def handle_event("begin", %{"org" => _org_handle}, socket) do
- # TODO: Implement contract acceptance logic
- {:noreply, socket}
- end
-
- def handle_event("begin_collaboration", _, socket) do
- {:noreply, assign(socket, :show_begin_collaboration_drawer, true)}
- end
-
- def handle_event("submit_collaboration", _params, socket) do
- # TODO: Implement payment method addition and collaboration initiation
- {:noreply, socket}
- end
-
- def handle_event("close_drawer", _, socket) do
- {:noreply, assign(socket, :show_begin_collaboration_drawer, false)}
- end
-
- def handle_event("validate_job", %{"job_form" => params}, socket) do
- form =
- %JobForm{}
- |> JobForm.changeset(params)
- |> Map.put(:action, :validate)
- |> to_form(as: :job_form)
-
- {:noreply, assign(socket, job_form: form)}
- end
-
- def handle_event("create_job", %{"job_form" => params}, socket) do
- case Jobs.create_job(params) do
- {:ok, job} ->
- {:noreply,
- socket
- |> put_flash(:info, "Job created successfully")
- |> redirect(to: ~p"/jobs/#{job}")}
-
- {:error, %Ecto.Changeset{} = changeset} ->
- {:noreply, assign(socket, job_form: to_form(changeset, as: :job_form))}
- end
- end
-
- def compact_view(assigns) do
- ~H"""
-
-
-
-
- {Money.to_string!(@bounty.amount)}
-
-
- <.link
- href={Bounty.url(@bounty)}
- class="max-w-[400px] truncate text-sm text-foreground hover:underline"
- >
- {@bounty.ticket.title}
-
-
-
- <.link navigate={User.url(@bounty.owner)} class="font-semibold hover:underline">
- {@bounty.owner.name}
-
- <.icon name="tabler-chevron-right" class="h-4 w-4" />
- <.link href={Bounty.url(@bounty)} class="hover:underline">
- {Bounty.path(@bounty)}
-
-
-
-
-
- """
- end
-
- def contract(assigns) do
- ~H"""
-
-
-
-
- <.link navigate={User.url(@contract.client)}>
- <.avatar class="aspect-[1200/630] h-32 w-auto rounded-lg">
- <.avatar_image
- src={@contract.client.og_image_url || @contract.client.avatar_url}
- alt={@contract.client.name}
- class="object-cover"
- />
- <.avatar_fallback class="rounded-lg">
-
-
-
-
-
- <.link navigate={User.url(@contract.client)} class="font-semibold hover:underline">
- {@contract.client.og_title || @contract.client.name}
-
-
-
- {@contract.client.bio}
-
-
-
-
- {Money.to_string!(@contract.hourly_rate_min)} - {Money.to_string!(
- @contract.hourly_rate_max
- )}/hr
-
-
- · {@contract.hours_per_week} hours/week
-
-
-
-
- <%= for tag <- @contract.client.tech_stack do %>
-
- {tag}
-
- <% end %>
-
-
-
-
-
-
- """
- end
-
- def default_view(assigns) do
- ~H"""
-
-
-
- <.link href={}>
- <.avatar class="h-14 w-14 rounded-xl">
- <.avatar_image src={@bounty.owner.avatar_url} alt={@bounty.owner.name} />
- <.avatar_fallback>
- {Algora.Util.initials(@bounty.owner.name)}
-
-
-
-
-
-
- <.link href={} class="font-semibold hover:underline">
- {@bounty.owner.name}
-
- <.icon name="tabler-chevron-right" class="h-4 w-4" />
- <.link href={Bounty.url(@bounty)} class="hover:underline">
- {Bounty.path(@bounty)}
-
-
-
- <.link href={Bounty.url(@bounty)} class="group flex items-center gap-2">
-
- {Money.to_string!(@bounty.amount)}
-
-
- {@bounty.ticket.title}
-
-
-
-
- <%= for tag <- @bounty.tech_stack do %>
-
- #{tag}
-
- <% end %>
-
-
-
-
-
- """
- end
-
- def matching_dev(assigns) do
- ~H"""
-
-
-
-
- <.link navigate={User.url(@user)}>
- <.avatar class="h-20 w-20 rounded-full">
- <.avatar_image src={@user.avatar_url} alt={@user.name} />
- <.avatar_fallback class="rounded-lg">
- {Algora.Util.initials(@user.name)}
-
-
-
-
-
-
- <.link navigate={User.url(@user)} class="font-semibold hover:underline">
- {@user.name}
-
-
-
-
-
- {Money.to_string!(@user.hourly_rate_max)}/hr
-
-
-
-
- <%= for tech <- @user.tech_stack do %>
-
- {tech}
-
- <% end %>
-
-
-
-
- <%= if @user.review do %>
-
- <%= for i <- 1..5 do %>
- <.icon
- name="tabler-star-filled"
- class={"#{if i <= @user.review.rating, do: "text-foreground", else: "text-muted-foreground/25"} h-4 w-4"}
- />
- <% end %>
-
-
{@user.review.content}
-
- <.avatar class="h-8 w-8">
- <.avatar_image
- src={@user.review.reviewer.avatar_url}
- alt={@user.review.reviewer.name}
- />
- <.avatar_fallback>
- {Algora.Util.initials(@user.review.reviewer.name)}
-
-
-
-
{@user.review.reviewer.name}
-
- {@user.review.organization.name}
-
-
-
- <% else %>
-
No reviews yet
- <% end %>
-
-
-
-
- """
- end
-
- defp fetch_matching_devs(tech_stack) do
- developers =
- Accounts.list_developers(
- sort_by_tech_stack: tech_stack,
- limit: 3,
- earnings_gt: Money.new!(200, "USD")
- )
-
- reviews = developers |> Enum.map(& &1.id) |> Reviews.get_top_reviews_for_users()
- Enum.map(developers, fn dev -> Map.put(dev, :review, Map.get(reviews, dev.id)) end)
- end
-end
diff --git a/lib/algora_web/live/org/forms/job_form.ex b/lib/algora_web/live/org/forms/job_form.ex
deleted file mode 100644
index 4c98257d6..000000000
--- a/lib/algora_web/live/org/forms/job_form.ex
+++ /dev/null
@@ -1,33 +0,0 @@
-defmodule AlgoraWeb.Org.Forms.JobForm do
- @moduledoc false
- use Ecto.Schema
-
- import Ecto.Changeset
-
- embedded_schema do
- field :title, :string
- field :ticket_url, :string
- field :work_type, :string, default: "remote"
- field :min_compensation, :integer
- field :max_compensation, :integer
-
- embeds_many :projects, Project do
- field :title, :string
- field :url, :string
- field :amount, :integer
- end
- end
-
- def changeset(form, attrs \\ %{}) do
- form
- |> cast(attrs, [:title, :ticket_url, :work_type, :min_compensation, :max_compensation])
- |> validate_required([:title, :ticket_url, :work_type])
- |> cast_embed(:projects, with: &project_changeset/2)
- end
-
- defp project_changeset(schema, attrs) do
- schema
- |> cast(attrs, [:title, :url, :amount])
- |> validate_required([:title, :url, :amount])
- end
-end
diff --git a/lib/algora_web/live/org/job_live.ex b/lib/algora_web/live/org/job_live.ex
deleted file mode 100644
index 7fca8ba44..000000000
--- a/lib/algora_web/live/org/job_live.ex
+++ /dev/null
@@ -1,363 +0,0 @@
-defmodule AlgoraWeb.Org.JobLive do
- @moduledoc false
- use AlgoraWeb, :live_view
-
- alias Algora.Accounts
- alias Algora.Accounts.User
- alias Algora.Bounties
- alias Algora.Bounties.Bounty
-
- def mount(_params, _session, socket) do
- job = %{
- title: "Senior Elixir Developer for Video Processing Platform",
- description: """
- We're seeking an experienced Elixir developer to help build and scale our video processing platform. You'll work on developing high-performance video processing pipelines using FFmpeg and implementing real-time features with Phoenix LiveView.
-
- Key Responsibilities:
- • Design and implement scalable backend services in Elixir/Phoenix
- • Optimize video processing workflows using FFmpeg
- • Write clean, maintainable, and well-tested code
- • Collaborate with the team on architecture decisions
- • Mentor junior developers
-
- Requirements:
- • 5+ years of software development experience
- • Strong experience with Elixir and Phoenix Framework
- • Familiarity with FFmpeg and video processing concepts
- • Experience with PostgreSQL and database optimization
- • Knowledge of modern frontend technologies (TailwindCSS)
- • Excellent problem-solving and communication skills
-
- We offer competitive compensation, flexible remote work, and the opportunity to work on challenging technical problems at scale.
- """,
- tech_stack: ["Elixir", "Phoenix", "PostgreSQL", "TailwindCSS", "FFmpeg"],
- country: socket.assigns.current_country
- }
-
- nav_items = [
- %{
- icon: "tabler-home",
- label: "Dashboard",
- href: "#",
- active: true
- },
- %{
- icon: "tabler-diamond",
- label: "Bountes",
- href: "#",
- active: false
- },
- %{
- icon: "tabler-file",
- label: "Documents",
- href: "#",
- active: false
- },
- %{
- icon: "tabler-users",
- label: "Team",
- href: "#",
- active: false
- }
- ]
-
- footer_nav_items = [
- %{
- icon: "tabler-settings",
- label: "Settings",
- href: "#"
- }
- ]
-
- user_menu_items = [
- %{label: "My Account", href: "#", divider: true},
- %{label: "Settings", href: "#"},
- %{label: "Support", href: "#", divider: true},
- %{label: "Logout", href: "#"}
- ]
-
- filter_menu_items = [
- %{label: "Fulfilled", href: "#"},
- %{label: "Declined", href: "#"},
- %{label: "Refunded", href: "#"}
- ]
-
- time_periods = [
- %{label: "Week", value: "week"},
- %{label: "Month", value: "month"},
- %{label: "Year", value: "year"}
- ]
-
- matching_devs =
- Accounts.list_developers(
- limit: 5,
- sort_by_country: job.country,
- sort_by_tech_stack: job.tech_stack,
- earnings_gt: Money.new!(200, "USD")
- )
-
- bounties = Bounties.list_bounties(limit: 8)
-
- {:ok,
- assign(socket,
- page_title: "Job",
- job: job,
- show_full_description: false,
- nav_items: nav_items,
- footer_nav_items: footer_nav_items,
- user_menu_items: user_menu_items,
- filter_menu_items: filter_menu_items,
- time_periods: time_periods,
- active_period: "week",
- matching_devs: matching_devs,
- bounties: bounties
- )}
- end
-
- def render(assigns) do
- ~H"""
-
-
-
-
-
-
-
-
-
-
- {@job.title}
-
-
-
- <%= for tech <- @job.tech_stack do %>
-
- {tech}
-
- <% end %>
-
-
-
-
- <.icon name="tabler-clock" class="h-4 w-4" /> Posted March 15, 2024
-
-
- <.icon name="tabler-world" class="h-4 w-4" /> {@job.country}
-
-
-
- <%= if @job[:hourly_rate] do %>
-
-
- {Money.to_string!(@job.hourly_rate)}/hour
-
-
- Hourly Rate
-
-
- <% end %>
-
-
-
-
-
- <%= if @show_full_description do %>
- {@job.description}
- <% else %>
- {String.split(@job.description, "\n") |> Enum.take(3) |> Enum.join("\n")}...
- <% end %>
-
-
- {if @show_full_description, do: "Show less", else: "See more"}
-
-
-
-
-
-
-
-
-
-
-
- Bounties
-
-
- Bounties linked to your job
-
-
-
-
-
-
-
- Ticket
-
-
-
-
- <%= if @bounties == [] do %>
-
-
-
-
- <.icon name="tabler-plus" class="h-6 w-6 text-primary" />
-
-
No Bounties Yet
-
- Create your first bounty to start attracting developers to your job.
-
- <.button>
- <.icon name="tabler-plus" class="mr-2 h-4 w-4" /> Add Bounty
-
-
-
-
- <% else %>
- <%= for bounty <- @bounties do %>
-
-
-
-
- {Money.to_string!(bounty.amount)}
-
-
- <.link
- href={Bounty.url(bounty)}
- class="max-w-[400px] truncate text-sm text-foreground hover:underline"
- >
- {bounty.ticket.title}
-
-
-
- <.link
- navigate={User.url(bounty.owner)}
- class="font-semibold hover:underline"
- >
- {bounty.owner.name}
-
- <.icon name="tabler-chevron-right" class="h-4 w-4" />
- <.link href={Bounty.url(bounty)} class="hover:underline">
- {Bounty.path(bounty)}
-
-
-
-
-
- <% end %>
- <% end %>
-
-
-
-
-
-
-
-
-
-
-
-
- <.icon name="tabler-users-plus" class="h-6 w-6 text-primary" />
-
-
Invite Developers
-
- Share this job with developers in your network or invite them directly.
-
-
- <.button>
- Invite Developers
-
- <.button variant="outline">
- Share Link
-
-
-
-
-
-
- <%= if @matching_devs != [] do %>
-
-
-
- Matching Developers
-
-
-
- <%= for dev <- @matching_devs do %>
-
-
-
-
-
-
- {dev.name} {dev.flag}
-
-
- @{User.handle(dev)}
-
-
-
-
Earned
-
- {Money.to_string!(dev.total_earned)}
-
-
-
-
-
- <%= for tech <- dev.tech_stack do %>
-
- {tech}
-
- <% end %>
-
-
-
- <% end %>
-
-
-
- <% end %>
-
-
-
-
- """
- end
-
- def handle_event("select_period", %{"period" => period}, socket) do
- {:noreply, assign(socket, active_period: period)}
- end
-
- def handle_event("toggle_description", _, socket) do
- {:noreply, assign(socket, show_full_description: !socket.assigns.show_full_description)}
- end
-end
diff --git a/lib/algora_web/live/org/projects_live.ex b/lib/algora_web/live/org/projects_live.ex
deleted file mode 100644
index dd2ca82ed..000000000
--- a/lib/algora_web/live/org/projects_live.ex
+++ /dev/null
@@ -1,16 +0,0 @@
-defmodule AlgoraWeb.Org.ProjectsLive do
- @moduledoc false
- use AlgoraWeb, :live_view
-
- on_mount AlgoraWeb.Org.BountyHook
-
- def mount(_params, _session, socket) do
- {:ok, socket}
- end
-
- def render(assigns) do
- ~H"""
- Projects
- """
- end
-end
diff --git a/lib/algora_web/live/project/create_live.ex b/lib/algora_web/live/project/create_live.ex
deleted file mode 100644
index 9b827dd23..000000000
--- a/lib/algora_web/live/project/create_live.ex
+++ /dev/null
@@ -1,341 +0,0 @@
-defmodule AlgoraWeb.Project.CreateLive do
- @moduledoc false
- use AlgoraWeb, :live_view
-
- alias Algora.Accounts
- alias Algora.Accounts.User
-
- def mount(_params, session, socket) do
- project = %{
- country: socket.assigns.current_country,
- tech_stack: ["Elixir"],
- title: "",
- visibility: :public
- }
-
- {:ok,
- socket
- |> assign(step: 1)
- |> assign(total_steps: 2)
- |> assign(project: project)
- |> assign(current_user: %{email: session["user_email"]})
- |> assign(matching_devs: get_matching_devs(project))}
- end
-
- def render(assigns) do
- ~H"""
-
-
-
-
- {@step} / {@total_steps}
-
Create Your Project
-
-
- {render_step(assigns)}
-
-
- <%= if @step > 1 do %>
-
- Previous
-
- <% else %>
-
- <% end %>
- <%= if @step < @total_steps do %>
-
- Next: {next_step_label(@step)}
-
- <% else %>
-
- Initialize Project
-
- <% end %>
-
-
-
-
-
-
- Matching Developers
-
- <%= if @matching_devs == [] do %>
-
Add tech_stack to see matching developers
- <% else %>
- <%= for dev <- @matching_devs do %>
-
-
-
-
-
-
-
- {dev.name} {dev.flag}
-
-
@{User.handle(dev)}
-
-
-
Earned
-
- {Money.to_string!(dev.total_earned)}
-
-
-
-
-
-
- <%= for tech <- dev.tech_stack do %>
-
- {tech}
-
- <% end %>
-
-
-
-
-
- <% end %>
- <% end %>
-
-
- """
- end
-
- def render_step(%{step: 1} = assigns) do
- ~H"""
-
-
- What is your tech stack?
-
-
-
-
-
-
-
- <%= for tech <- ["Elixir", "Phoenix", "Phoenix LiveView", "PostgreSQL"] do %>
-
- {tech}
-
- ×
-
-
- <% end %>
-
-
- """
- end
-
- def render_step(%{step: 2} = assigns) do
- ~H"""
-
-
Project Details
-
-
- Title
-
-
-
-
- Discovery
-
-
-
-
- """
- end
-
- defp update_project_field(project, "tech_stack", _value, %{"tech" => tech}) do
- tech_stack =
- if tech in project.tech_stack,
- do: List.delete(project.tech_stack, tech),
- else: [tech | project.tech_stack]
-
- %{project | tech_stack: tech_stack}
- end
-
- defp update_project_field(project, "scope." <> scope_field, value, _params) do
- scope = Map.put(project.scope, String.to_atom(scope_field), value)
- %{project | scope: scope}
- end
-
- defp update_project_field(project, "budget.type", value, _params) do
- new_budget =
- case value do
- "fixed" -> %{type: :fixed, fixed_price: nil}
- "hourly" -> %{type: :hourly, hourly_rate: nil, hours_per_week: nil}
- end
-
- %{project | budget: new_budget}
- end
-
- defp update_project_field(project, "budget." <> budget_field, value, _params) do
- budget = Map.put(project.budget, String.to_atom(budget_field), value)
- %{project | budget: budget}
- end
-
- defp update_project_field(project, "visibility", value, _params) do
- %{project | visibility: String.to_atom(value)}
- end
-
- defp update_project_field(project, field, value, _params) do
- Map.put(project, 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)}
- end
-
- def handle_event("submit", _, socket) do
- # Handle project submission
- {:noreply, socket}
- end
-
- def handle_event("add_tech", %{"tech" => tech}, socket) do
- updated_tech_stack = Enum.uniq([tech | socket.assigns.project.tech_stack])
- updated_project = Map.put(socket.assigns.project, :tech_stack, updated_tech_stack)
- {:noreply, assign(socket, project: updated_project)}
- end
-
- def handle_event("remove_tech", %{"tech" => tech}, socket) do
- updated_tech_stack = List.delete(socket.assigns.project.tech_stack, tech)
- updated_project = Map.put(socket.assigns.project, :tech_stack, updated_tech_stack)
- {:noreply, assign(socket, project: updated_project)}
- end
-
- def handle_event("update_project", %{"field" => field, "value" => value} = params, socket) do
- updated_project = update_project_field(socket.assigns.project, field, value, params)
- matching_devs = get_matching_devs(updated_project)
- {:noreply, assign(socket, project: updated_project, matching_devs: matching_devs)}
- end
-
- defp next_step_label(1), do: "Project Details"
- defp next_step_label(2), do: "Collaboration"
-
- defp get_matching_devs(project) do
- Accounts.list_developers(
- limit: 5,
- sort_by_country: project.country,
- sort_by_tech_stack: project.tech_stack,
- earnings_gt: Money.new!(200, "USD")
- )
- end
-end
diff --git a/lib/algora_web/live/project/index_live.ex b/lib/algora_web/live/project/index_live.ex
deleted file mode 100644
index 0489b23cc..000000000
--- a/lib/algora_web/live/project/index_live.ex
+++ /dev/null
@@ -1,185 +0,0 @@
-defmodule AlgoraWeb.Project.IndexLive do
- @moduledoc false
- use AlgoraWeb, :live_view
-
- def mount(_params, _session, socket) do
- projects = [
- %{
- id: 1,
- title: "Build Real-time Chat Application",
- country: "US",
- tech_stack: ["Elixir", "Phoenix", "Phoenix LiveView", "PostgreSQL"],
- scope: %{size: "medium", duration: "medium", experience: "intermediate"},
- budget: %{type: :hourly, from: Money.new!(50, :USD), to: Money.new!(75, :USD)},
- description: "Looking for an experienced developer to build a real-time chat system...",
- posted_at: ~N[2024-03-15 10:00:00],
- client: %{
- name: "John Doe",
- avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=John",
- projects_posted: 5
- }
- },
- %{
- id: 2,
- title: "E-commerce Platform Development",
- country: "UK",
- tech_stack: ["Elixir", "Phoenix", "PostgreSQL", "JavaScript"],
- scope: %{size: "large", duration: "long", experience: "expert"},
- budget: %{type: :fixed, from: Money.new!(15_000, :USD), to: Money.new!(20_000, :USD)},
- description: "Need to build a scalable e-commerce platform...",
- posted_at: ~N[2024-03-14 15:30:00],
- client: %{
- name: "Sarah Smith",
- avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=Sarah",
- projects_posted: 3
- }
- }
- ]
-
- projects =
- Enum.map(projects, fn project ->
- Map.put(project, :applicants, [
- %{
- name: "Alice Johnson",
- designation: "Senior Developer",
- image: "https://api.dicebear.com/7.x/avataaars/svg?seed=Alice"
- },
- %{
- name: "Bob Smith",
- designation: "Full Stack Engineer",
- image: "https://api.dicebear.com/7.x/avataaars/svg?seed=Bob"
- },
- %{
- name: "Charlie Davis",
- designation: "Senior Developer",
- image: "https://api.dicebear.com/7.x/avataaars/svg?seed=Charlie"
- },
- %{
- name: "Dave Johnson",
- designation: "Full Stack Engineer",
- image: "https://api.dicebear.com/7.x/avataaars/svg?seed=Dave"
- }
- ])
- end)
-
- {:ok, assign(socket, projects: projects)}
- end
-
- def render(assigns) do
- ~H"""
-
-
-
Available Projects
-
-
- <%= for project <- @projects do %>
-
-
-
-
- <.link
- navigate={~p"/projects/#{project.id}"}
- class="transition hover:text-indigo-400"
- >
- {project.title}
-
-
-
-
- <.icon name="tabler-clock" class="h-4 w-4" />
- {Calendar.strftime(project.posted_at, "%B %d, %Y")}
-
-
- <.icon name="tabler-world" class="h-4 w-4" />
- {project.country}
-
-
-
-
-
- <%= case project.budget.type do %>
- <% :hourly -> %>
- ${project.budget.from}-{project.budget.to}/hour
- <% :fixed -> %>
- {Money.to_string!(project.budget.from)}-{Money.to_string!(project.budget.to)}
- <% end %>
-
-
- {String.capitalize("#{project.budget.type}")} Rate
-
-
-
-
-
- {project.description}
-
-
-
- <%= for tech <- project.tech_stack do %>
-
- {tech}
-
- <% end %>
-
-
-
-
-
-
-
{project.client.name}
-
- {project.client.projects_posted} projects posted
-
-
-
-
-
-
- <%= for applicant <- project.applicants do %>
-
-
-
-
-
-
-
- {applicant.name}
-
-
{applicant.designation}
-
-
-
- <% end %>
-
-
-
-
- <.icon name="tabler-chart-bar" class="h-4 w-4" />
- {String.capitalize(project.scope.experience)}
-
-
- <.icon name="tabler-clock" class="h-4 w-4" />
- {String.capitalize(project.scope.duration)} term
-
-
- <.icon name="tabler-layout-grid" class="h-4 w-4" />
- {String.capitalize(project.scope.size)} size
-
-
-
-
-
- <% end %>
-
-
-
- """
- end
-end
diff --git a/lib/algora_web/live/project/view_live.ex b/lib/algora_web/live/project/view_live.ex
deleted file mode 100644
index e7153c78a..000000000
--- a/lib/algora_web/live/project/view_live.ex
+++ /dev/null
@@ -1,258 +0,0 @@
-defmodule AlgoraWeb.Project.ViewLive do
- @moduledoc false
- use AlgoraWeb, :live_view
-
- alias Algora.Accounts
- alias Algora.Accounts.User
-
- def mount(%{"id" => id}, _session, socket) do
- # Mock data for a single project
- project = %{
- id: id,
- title: "Build Real-time Chat Application",
- country: socket.assigns.current_country,
- tech_stack: ["Elixir", "Phoenix", "Phoenix LiveView", "PostgreSQL"],
- budget: %{type: :hourly, from: Money.new!(50, :USD), to: Money.new!(75, :USD)},
- description: "Looking for an experienced developer to build a real-time chat system...",
- posted_at: ~N[2024-03-15 10:00:00],
- client: %{
- name: "John Doe",
- avatar_url: "https://api.dicebear.com/7.x/avataaars/svg?seed=John",
- projects_posted: 5
- },
- step: 1,
- total_steps: 5
- }
-
- matching_devs =
- Accounts.list_developers(
- limit: 6,
- sort_by_country: project.country,
- sort_by_tech_stack: project.tech_stack,
- earnings_gt: Money.new!(200, "USD")
- )
-
- {:ok,
- socket
- |> assign(:project, project)
- |> assign(:matching_devs, matching_devs)}
- end
-
- def render(assigns) do
- ~H"""
-
-
- <%!-- Project Header Card (Keep existing) --%>
-
-
-
-
- {@project.title}
-
-
-
- <%= for tech <- @project.tech_stack do %>
-
- {tech}
-
- <% end %>
-
-
-
-
- <.icon name="tabler-clock" class="h-4 w-4" />
- Posted {Calendar.strftime(@project.posted_at, "%B %d, %Y")}
-
-
- <.icon name="tabler-world" class="h-4 w-4" />
- {@project.country}
-
-
-
-
-
- <%= case @project.budget.type do %>
- <% :hourly -> %>
- ${@project.budget.from}-{@project.budget.to}/hour
- <% :fixed -> %>
- {Money.to_string!(@project.budget.from)}-{Money.to_string!(@project.budget.to)}
- <% end %>
-
-
- {String.capitalize("#{@project.budget.type}")} Rate
-
-
-
-
-
- <%!-- Project Tabs --%>
- <.tabs :let={builder} id="project-tabs" default="overview">
- <.tabs_list class="flex w-full space-x-1 rounded-lg bg-white/5 p-1">
- <.tabs_trigger builder={builder} value="overview" class="flex-1">
- <.icon name="tabler-layout-dashboard" class="mr-2 h-4 w-4" /> Overview
-
- <.tabs_trigger builder={builder} value="invitations" class="flex-1">
- <.icon name="tabler-users" class="mr-2 h-4 w-4" /> Invitations
-
- <.tabs_trigger builder={builder} value="documents" class="flex-1">
- <.icon name="tabler-file" class="mr-2 h-4 w-4" /> Documents
-
- <.tabs_trigger builder={builder} value="bounties" class="flex-1">
- <.icon name="tabler-dia" class="mr-2 h-4 w-4" /> Bounties
-
- <.tabs_trigger builder={builder} value="payments" class="flex-1">
- <.icon name="tabler-credit-card" class="mr-2 h-4 w-4" /> Payments
-
-
-
- <%!-- Overview Tab --%>
- <.tabs_content value="overview">
-
- <%!-- Project Description --%>
- <.card>
- <.card_header>
- <.card_title>Project Description
-
- <.card_content>
-
{@project.description}
-
-
-
- <%!-- Matching Developers Section (Keep existing) --%>
-
-
-
Best Matches
- <.button>
- <.icon name="tabler-user-plus" class="mr-2 h-4 w-4" /> Invite Match
-
-
-
- <%= for dev <- @matching_devs do %>
-
-
-
-
-
-
-
{dev.name} {dev.flag}
-
@{User.handle(dev)}
-
-
-
Earned
-
- {Money.to_string!(dev.total_earned)}
-
-
-
-
- <%= for tech <- dev.tech_stack do %>
-
- {tech}
-
- <% end %>
-
-
-
-
- <% end %>
-
-
-
-
-
- <%!-- Invitations Tab --%>
- <.tabs_content value="invitations">
-
- <.card>
- <.card_header>
- <.card_title>Invitations
-
- <.card_content>
-
- <.icon name="tabler-users" class="mx-auto mb-4 h-12 w-12 text-gray-400" />
-
No invitations yet
-
Start by inviting developers to your project
- <.button>
- <.icon name="tabler-user-plus" class="mr-2 h-4 w-4" />
- Discover & Invite Developers
-
-
-
-
-
-
-
- <%!-- Documents Tab --%>
- <.tabs_content value="documents">
-
- <.card>
- <.card_header>
- <.card_title>Project Documents
-
- <.card_content>
-
- <.icon name="tabler-file-upload" class="mx-auto mb-4 h-12 w-12 text-gray-400" />
-
No documents uploaded
-
- Upload NDAs, specifications, or other project documents
-
- <.button>
- <.icon name="tabler-upload" class="mr-2 h-4 w-4" /> Upload Documents
-
-
-
-
-
-
-
- <%!-- Bounties Tab --%>
- <.tabs_content value="bounties">
-
- <.card>
- <.card_header>
- <.card_title>Project Bounties
-
- <.card_content>
-
- <.icon name="tabler-dia" class="mx-auto mb-4 h-12 w-12 text-gray-400" />
-
No bounties created
-
- Break down your project into bounties for developers
-
- <.button>
- <.icon name="tabler-plus" class="mr-2 h-4 w-4" /> Create First Bounty
-
-
-
-
-
-
-
- <%!-- Payments Tab --%>
- <.tabs_content value="payments">
-
- <.card>
- <.card_header>
- <.card_title>Payment Setup
-
- <.card_content>
-
- <.icon name="tabler-credit-card" class="mx-auto mb-4 h-12 w-12 text-gray-400" />
-
Payment method required
-
- Add a payment method to start working with developers
-
- <.button>
- <.icon name="tabler-plus" class="mr-2 h-4 w-4" /> Add Payment Method
-
-
-
-
-
-
-
-
-
- """
- end
-end
diff --git a/lib/algora_web/live/regional_rankings_live.ex b/lib/algora_web/live/regional_rankings_live.ex
deleted file mode 100644
index 4efe2dcf0..000000000
--- a/lib/algora_web/live/regional_rankings_live.ex
+++ /dev/null
@@ -1,126 +0,0 @@
-defmodule AlgoraWeb.RegionalRankingsLive do
- @moduledoc false
- use AlgoraWeb, :live_view
-
- import Ecto.Query
-
- alias Algora.Accounts
- alias Algora.Accounts.User
- alias Algora.Misc.Regions
- alias Algora.Payments.Transaction
- alias Algora.Repo
-
- def mount(_params, _session, socket) do
- weeks = get_weekly_rankings(20)
- {:ok, assign(socket, :weeks, weeks)}
- end
-
- def render(assigns) do
- ~H"""
-
- <.header class="mb-8 text-3xl">
- Regional Rankings
- <:subtitle>Weekly top performers by region
-
-
-
- <%= for {week_start, rankings} <- @weeks do %>
- <.card>
- <.card_header>
-
-
- Week of {Calendar.strftime(
- DateTime.from_naive!(week_start, "Etc/UTC"),
- "%B %d, %Y"
- )}
-
-
-
-
-
- <%= for {region, user} <- rankings do %>
-
-
-
- #1 {region}
-
-
- {Money.to_string!(user.total_earned)}
-
-
-
- <.avatar class="h-12 w-12">
- <.avatar_image src={user.avatar_url} alt={user.name} />
- <.avatar_fallback>
- {Algora.Util.initials(user.name)}
-
-
-
-
- {user.name}
-
-
- @{User.handle(user)}
-
-
-
-
- <% end %>
-
-
-
- <% end %>
-
-
- """
- end
-
- defp get_weekly_rankings(num_weeks) do
- end_date = DateTime.utc_now()
- start_date = DateTime.add(end_date, -num_weeks * 7 * 24 * 60 * 60)
-
- # Get all transactions in date range
- transactions_query =
- from t in Transaction,
- join: u in Accounts.User,
- on: t.user_id == u.id,
- where: t.succeeded_at >= ^start_date and t.succeeded_at <= ^end_date,
- where: not is_nil(u.country),
- group_by: [
- u.id,
- u.name,
- u.handle,
- u.avatar_url,
- u.country,
- fragment("date_trunc('week', ?::timestamp)", t.succeeded_at)
- ],
- select: %{
- user_id: u.id,
- name: u.name,
- handle: u.handle,
- avatar_url: u.avatar_url,
- country: u.country,
- week: fragment("date_trunc('week', ?::timestamp)", t.succeeded_at),
- total_earned: sum(t.net_amount)
- }
-
- # Get transactions and organize by week and region
- transactions = Repo.all(transactions_query)
-
- # Group by week, then get top earner per region
- transactions
- |> Enum.group_by(& &1.week)
- |> Enum.map(fn {week, users} ->
- rankings =
- users
- |> Enum.group_by(&Regions.get_region(&1.country))
- |> Enum.map(fn {region, region_users} ->
- {region, Enum.max_by(region_users, & &1.total_earned)}
- end)
- |> Enum.sort_by(fn {region, _} -> region end)
-
- {week, rankings}
- end)
- |> Enum.sort_by(fn {week, _} -> NaiveDateTime.to_date(week) end, :desc)
- end
-end
diff --git a/lib/algora_web/live/tmp/pricing_live.ex b/lib/algora_web/live/tmp/pricing_live.ex
deleted file mode 100644
index fd4765b0a..000000000
--- a/lib/algora_web/live/tmp/pricing_live.ex
+++ /dev/null
@@ -1,796 +0,0 @@
-defmodule AlgoraWeb.Tmp.PricingLive do
- @moduledoc false
- use AlgoraWeb, :live_view
-
- defmodule Plan do
- @moduledoc false
- defstruct [
- :name,
- :description,
- :price,
- :cta_text,
- :popular,
- :previous_tier,
- :features,
- :footnote
- ]
- end
-
- defmodule Feature do
- @moduledoc false
- defstruct [:name, :detail]
- end
-
- defmodule ComputeOption do
- @moduledoc false
- defstruct [:name, :cpu, :memory, :price]
- end
-
- defmodule FaqItem do
- @moduledoc false
- defstruct [:id, :question, :answer]
- end
-
- defmodule ROIEstimate do
- @moduledoc false
- defstruct [
- :developers,
- :hourly_rate,
- :hours_per_week,
- :annual_tc,
- :platform_fee,
- :traditional_cost,
- :traditional_overhead,
- :traditional_total,
- :algora_cost,
- :algora_fee,
- :monthly_subscription,
- :algora_total,
- :savings
- ]
- end
-
- def mount(_params, _session, socket) do
- initial_estimate =
- calculate_roi_estimate(%{
- "developers" => "3",
- "hourly_rate" => "75",
- "hours_per_week" => "40"
- })
-
- socket =
- assign(socket,
- page_title: "Pricing",
- plans: get_plans(),
- faq_items: get_faq_items(),
- testimonials: get_testimonials(),
- page_scroll: 0,
- active_faq: nil,
- roi_estimate: initial_estimate
- )
-
- {:ok, socket}
- end
-
- def handle_event("select_plan", %{"plan" => plan_name}, socket) do
- {:noreply, push_navigate(socket, to: "/signup?plan=#{plan_name}")}
- end
-
- def handle_event("toggle_faq", %{"id" => faq_id}, socket) do
- active_faq = if socket.assigns.active_faq == faq_id, do: nil, else: faq_id
- {:noreply, assign(socket, active_faq: active_faq)}
- end
-
- def handle_event("select_compute", %{"option" => option}, socket) do
- {:noreply, assign(socket, selected_compute_option: option)}
- end
-
- def handle_event("calculate_roi", %{"roi" => params}, socket) do
- params = calculate_missing_rate_field(params)
- estimate = calculate_roi_estimate(params)
- {:noreply, assign(socket, roi_estimate: estimate)}
- end
-
- defp calculate_missing_rate_field(params) do
- hours_per_week = params["hours_per_week"] |> to_string() |> String.trim()
- hourly_rate = params["hourly_rate"] |> to_string() |> String.trim()
- annual_tc = params["annual_tc"] |> to_string() |> String.trim()
-
- # Convert empty strings to nil for clearer logic
- hours_per_week = if hours_per_week == "", do: nil, else: String.to_integer(hours_per_week)
- hourly_rate = if hourly_rate == "", do: nil, else: String.to_integer(hourly_rate)
- annual_tc = if annual_tc == "", do: nil, else: String.to_integer(annual_tc)
-
- cond do
- # Case 1: Annual TC and Hours/Week provided - calculate Hourly Rate
- is_nil(hourly_rate) && not is_nil(hours_per_week) && not is_nil(annual_tc) ->
- calculated_rate = div(annual_tc, hours_per_week * 52)
-
- %{
- "developers" => params["developers"],
- "hourly_rate" => to_string(calculated_rate),
- "hours_per_week" => to_string(hours_per_week),
- "annual_tc" => to_string(annual_tc)
- }
-
- # Case 2: Annual TC and Hourly Rate provided - calculate Hours/Week
- is_nil(hours_per_week) && not is_nil(hourly_rate) && not is_nil(annual_tc) ->
- calculated_hours = div(annual_tc, hourly_rate * 52)
-
- %{
- "developers" => params["developers"],
- "hourly_rate" => to_string(hourly_rate),
- "hours_per_week" => to_string(calculated_hours),
- "annual_tc" => to_string(annual_tc)
- }
-
- # Case 3: Hours/Week and Hourly Rate provided - calculate Annual TC
- is_nil(annual_tc) && not is_nil(hourly_rate) && not is_nil(hours_per_week) ->
- calculated_tc = hourly_rate * hours_per_week * 52
-
- %{
- "developers" => params["developers"],
- "hourly_rate" => to_string(hourly_rate),
- "hours_per_week" => to_string(hours_per_week),
- "annual_tc" => to_string(calculated_tc)
- }
-
- # Default case: return original params if we don't have enough information
- true ->
- params
- end
- end
-
- defp calculate_roi_estimate(params) do
- developers = String.to_integer(params["developers"])
- hourly_rate = String.to_integer(params["hourly_rate"])
- hours_per_week = String.to_integer(params["hours_per_week"])
- annual_tc = hourly_rate * hours_per_week * 52
-
- # Base monthly cost (same for both traditional and Algora)
- monthly_base_cost = developers * hourly_rate * hours_per_week * 4.3
-
- # Traditional hiring costs (35% overhead - industry average per SBA)
- traditional_cost = monthly_base_cost
- traditional_overhead = traditional_cost * 0.35
- traditional_total = traditional_cost + traditional_overhead
-
- # Algora costs
- platform_fee = 0.15
- monthly_subscription = 599
- algora_cost = monthly_base_cost
- algora_fee = algora_cost * platform_fee
- algora_total = algora_cost + algora_fee + monthly_subscription
-
- yearly_savings = (traditional_total - algora_total) * 12
-
- %ROIEstimate{
- developers: developers,
- hourly_rate: hourly_rate,
- hours_per_week: hours_per_week,
- annual_tc: annual_tc,
- platform_fee: platform_fee,
- traditional_cost: traditional_cost,
- traditional_overhead: traditional_overhead,
- traditional_total: traditional_total,
- algora_cost: algora_cost,
- algora_fee: algora_fee,
- monthly_subscription: monthly_subscription,
- algora_total: algora_total,
- savings: yearly_savings
- }
- end
-
- # Component: Pricing Card
- def pricing_card(assigns) do
- ~H"""
-
-
-
-
-
- {@plan.name}
-
- <%= if @plan.popular do %>
-
- Most Popular
-
- <% end %>
-
-
-
- {@plan.description}
-
-
- {@plan.cta_text}
-
-
-
- <%= if @plan.price do %>
-
-
From
-
-
${@plan.price}
-
/ month
-
-
- <% else %>
-
- <% end %>
-
-
-
-
-
- {if @plan.previous_tier,
- do: "Everything in the #{@plan.previous_tier} Plan, plus:",
- else: "Get started with:"}
-
-
- <%= for feature <- @plan.features do %>
-
-
-
- <.icon name="tabler-check" class="h-4 w-4 text-primary" />
-
-
{feature.name}
-
- <%= if feature.detail do %>
- {feature.detail}
- <% end %>
-
- <% end %>
-
- <%= if @plan.footnote do %>
-
- <% end %>
-
-
- """
- end
-
- # Component: Compute Add-ons
- def compute_addons(assigns) do
- ~H"""
-
-
-
Compute Add-ons
-
- Additional compute resources for demanding workloads
-
-
-
- <%= for option <- @compute_addons do %>
-
-
- {option.name}
-
-
CPU: {option.cpu}
-
Memory: {option.memory}
-
${option.price}/month
-
-
-
- <% end %>
-
-
- """
- end
-
- # Component: Plan Comparison Table
- def plan_comparison_table(assigns) do
- ~H"""
-
-
Compare Plans
-
-
-
-
- Features
- <%= for plan <- @plans do %>
-
- {plan.name}
-
- <% end %>
-
-
-
- <%= for feature <- get_comparison_features() do %>
-
- {feature.name}
- <%= for plan <- @plans do %>
-
- <%= if has_feature?(plan, feature) do %>
- <.icon name="tabler-check" class="mx-auto h-5 w-5 text-primary" />
- <% else %>
- <.icon name="tabler-minus" class="mx-auto h-5 w-5 text-muted-foreground" />
- <% end %>
-
- <% end %>
-
- <% end %>
-
-
-
-
- """
- end
-
- # Component: FAQ Section
- def faq_section(assigns) do
- ~H"""
-
-
- Frequently asked questions
-
-
- <%= for item <- @faq_items do %>
-
-
- {item.question}
- <.icon
- name="tabler-chevron-down"
- class={
- classes([
- "h-5 w-5 text-muted-foreground transition-transform duration-200",
- @active_faq == item.id && "rotate-180 transform"
- ])
- }
- />
-
- <%= if @active_faq == item.id do %>
-
- {item.answer}
-
- <% end %>
-
- <% end %>
-
-
- """
- end
-
- # New ROI Calculator Component
- def roi_calculator(assigns) do
- ~H"""
-
-
Calculate Your Savings
-
-
-
- <%= if @roi_estimate do %>
-
-
-
-
Traditional Hiring
-
-
- Base Monthly Cost
-
- ${Number.Delimit.number_to_delimited(trunc(@roi_estimate.traditional_cost))}
-
-
-
- Overhead (35%)
-
- ${Number.Delimit.number_to_delimited(trunc(@roi_estimate.traditional_overhead))}
-
-
-
- Total Monthly Cost
-
- ${Number.Delimit.number_to_delimited(trunc(@roi_estimate.traditional_total))}
-
-
-
- Total Yearly Cost
-
- ${Number.Delimit.number_to_delimited(trunc(@roi_estimate.traditional_total * 12))}
-
-
-
-
-
-
-
With Algora
-
-
- Base Monthly Cost
-
- ${Number.Delimit.number_to_delimited(trunc(@roi_estimate.algora_cost))}
-
-
-
-
- Platform Fee ({trunc(@roi_estimate.platform_fee * 100)}%)
-
-
- ${Number.Delimit.number_to_delimited(trunc(@roi_estimate.algora_fee))}
-
-
-
-
- Placement Fee (0%)
-
-
- $0.00
-
-
-
- Monthly Subscription
-
- ${Number.Delimit.number_to_delimited(@roi_estimate.monthly_subscription)}
-
-
-
- Total Monthly Cost
-
- ${Number.Delimit.number_to_delimited(trunc(@roi_estimate.algora_total))}
-
-
-
- Total Yearly Cost
-
- ${Number.Delimit.number_to_delimited(trunc(@roi_estimate.algora_total * 12))}
-
-
-
-
-
-
-
-
- Estimated Yearly Savings
-
- ${Number.Delimit.number_to_delimited(trunc(@roi_estimate.savings))}
-
-
-
- Savings include reduced recruitment costs, social security taxes, and administrative expenses.
-
-
-
- <% end %>
-
- """
- end
-
- # Data functions
- defp get_plans do
- [
- %Plan{
- name: "Hobby",
- description: "Perfect for small projects and indie developers",
- price: 0,
- cta_text: "Start for Free",
- popular: false,
- features: [
- %Feature{name: "Up to $5,000 in project budgets"},
- %Feature{name: "Algora Network", detail: "15% platform fee"},
- %Feature{name: "Bring Your Own Devs", detail: "5% platform fee"},
- %Feature{name: "Unlimited projects"},
- %Feature{name: "Community support"},
- %Feature{name: "Basic project management tools"},
- %Feature{name: "Pay per milestone"}
- ],
- footnote: "Perfect for testing the waters with smaller projects"
- },
- %Plan{
- name: "Startup",
- description: "For growing companies",
- price: 599,
- cta_text: "Upgrade now",
- popular: true,
- previous_tier: "Hobby",
- features: [
- %Feature{name: "Up to $50,000 in project budgets"},
- %Feature{name: "Algora Network", detail: "15% platform fee"},
- %Feature{name: "Bring Your Own Devs", detail: "5% platform fee"},
- %Feature{name: "Priority support"},
- %Feature{name: "Advanced project management"},
- %Feature{name: "Custom workflows"},
- %Feature{name: "Team collaboration tools"},
- %Feature{name: "Analytics dashboard"},
- %Feature{name: "Job board access"},
- %Feature{name: "Unlimited job postings"}
- ]
- },
- %Plan{
- name: "Enterprise",
- description: "For large organizations",
- price: nil,
- cta_text: "Contact Sales",
- popular: false,
- previous_tier: "Startup",
- features: [
- %Feature{name: "Unlimited project budgets"},
- %Feature{name: "Algora Network", detail: "15% platform fee"},
- %Feature{name: "Bring Your Own Devs", detail: "5% platform fee"},
- %Feature{name: "Dedicated account manager"},
- %Feature{name: "Custom contracts & MSA"},
- %Feature{name: "Advanced security features"},
- %Feature{name: "Custom integrations"},
- %Feature{name: "SLA guarantees"},
- %Feature{name: "Onboarding assistance"},
- %Feature{name: "Training for your team"},
- %Feature{name: "Custom job board"},
- %Feature{name: "ATS integration"}
- ]
- }
- ]
- end
-
- defp get_faq_items do
- [
- %FaqItem{
- id: "platform-fee",
- question: "How do the platform fees work?",
- answer:
- "We charge 15% for projects using the Algora Network of developers, or 5% if you bring your own developers. This fee helps us maintain the platform, provide payment protection, and ensure quality service."
- },
- %FaqItem{
- id: "budget-limits",
- question: "What happens if my project exceeds the budget limit?",
- answer:
- "You'll need to upgrade to a higher tier to handle larger project budgets. Contact our sales team if you're close to your limit."
- },
- %FaqItem{
- id: "payment-protection",
- question: "How does payment protection work?",
- answer:
- "We hold payments in escrow and release them based on project milestones. This ensures both clients and developers are protected throughout the project lifecycle."
- }
- ]
- end
-
- defp get_comparison_features do
- [
- %Feature{name: "Project Budget Limit"},
- %Feature{name: "Algora Network Fee"},
- %Feature{name: "Bring Your Own Devs Fee"},
- %Feature{name: "Support Level"},
- %Feature{name: "Team Management"},
- %Feature{name: "Custom Contracts"},
- %Feature{name: "Analytics"}
- ]
- end
-
- defp has_feature?(plan, feature) do
- Enum.any?(plan.features, &(&1.name == feature.name))
- end
-
- defp get_testimonials do
- [
- %{
- name: "Sarah Chen",
- role: "CTO at TechCorp",
- avatar: "https://images.unsplash.com/photo-1494790108377-be9c29b29330",
- quote:
- "Algora has transformed how we hire developers. The quality of talent and the seamless platform experience has made scaling our team effortless."
- },
- %{
- name: "Michael Rodriguez",
- role: "Engineering Lead at StartupX",
- avatar: "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e",
- quote:
- "The developers we found through Algora have become integral parts of our team. The platform's focus on open source contributors really makes a difference."
- },
- %{
- name: "Emily Thompson",
- role: "VP Engineering at ScaleUp Inc",
- avatar: "https://images.unsplash.com/photo-1438761681033-6461ffad8d80",
- quote:
- "What impressed me most was how quickly we could find and onboard qualified developers. Algora's platform streamlines the entire process."
- }
- ]
- end
-
- def render(assigns) do
- ~H"""
-
-
-
-
-
- Predictable pricing, designed to scale
-
-
- Start building for free, collaborate with your team, then scale to millions of users
-
-
-
-
-
-
-
-
- <%= for plan <- @plans do %>
- <.pricing_card plan={plan} />
- <% end %>
-
-
- <.roi_calculator roi_estimate={@roi_estimate} />
-
-
-
-
-
-
- Trusted by companies worldwide
-
-
- See what our customers have to say about their experience with Algora
-
-
-
- <%= for testimonial <- @testimonials do %>
-
-
-
-
-
{testimonial.name}
-
{testimonial.role}
-
-
-
- {testimonial.quote}
-
-
- <% end %>
-
-
-
-
-
-
- Frequently asked questions
-
-
- <%= for item <- @faq_items do %>
-
-
- {item.question}
- <.icon
- name="tabler-chevron-down"
- class={
- classes([
- "h-5 w-5 text-muted-foreground transition-transform duration-200",
- @active_faq == item.id && "rotate-180 transform"
- ])
- }
- />
-
- <%= if @active_faq == item.id do %>
-
- {item.answer}
-
- <% end %>
-
- <% end %>
-
-
-
-
-
-
-
- The open source
- UpWork alternative.
-
-
- <.link
- navigate="/signup"
- class="inline-flex h-10 items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50"
- >
- Start your project
-
- <.link
- navigate="/contact/sales"
- class="inline-flex h-10 items-center justify-center rounded-md border border-input bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50"
- >
- Request a demo
-
-
-
-
-
- """
- end
-end
diff --git a/lib/algora_web/live/trotw_live.ex b/lib/algora_web/live/trotw_live.ex
deleted file mode 100644
index 8642d1f1f..000000000
--- a/lib/algora_web/live/trotw_live.ex
+++ /dev/null
@@ -1,209 +0,0 @@
-defmodule AlgoraWeb.TROTWLive do
- @moduledoc false
- use AlgoraWeb, :live_view
-
- import Ecto.Query
-
- alias Algora.Accounts
- alias Algora.Accounts.User
- alias Algora.Misc.Regions
- alias Algora.Payments.Transaction
- alias Algora.Repo
-
- def mount(_params, _session, socket) do
- weeks = get_weekly_rankings()
- medals = calculate_medals(weeks)
- {:ok, assign(socket, weeks: weeks, medals: medals)}
- end
-
- def render(assigns) do
- ~H"""
-
- <.header class="mb-8 text-center">
-
TROTW
- <:subtitle>
-
Top Regions of the Week
-
-
-
- <.card class="mb-8">
- <.card_header>
-
All-Time Medal Count
-
-
-
- <%= for {region, medals} <- Enum.sort_by(@medals, fn {_, m} -> m.total end, :desc) do %>
-
-
-
{region}
-
-
- 🥇
- {medals.gold}
-
-
- 🥈
- {medals.silver}
-
-
- 🥉
- {medals.bronze}
-
-
-
-
- <% end %>
-
-
-
-
-
- <%= for {week_start, rankings} <- @weeks do %>
- <.card>
- <.card_header>
-
-
- Week of {Calendar.strftime(
- DateTime.from_naive!(week_start, "Etc/UTC"),
- "%B %d, %Y"
- )}
-
-
-
-
-
- <%= for {{region, total_earned, top_earners}, index} <- rankings |> Enum.take(3) |> Enum.with_index(1) do %>
-
-
-
-
- {case index do
- 1 -> "🥇"
- 2 -> "🥈"
- 3 -> "🥉"
- end}
-
- {region}
-
-
- {Money.to_string!(total_earned)}
-
-
-
-
- <%= for earner <- top_earners do %>
- <.tooltip>
- <.tooltip_trigger>
-
-
-
-
- <.tooltip_content>
-
- @{User.handle(earner)}
-
- {Money.to_string!(earner.total_earned)}
-
-
-
-
- <% end %>
-
-
- <% end %>
-
-
-
- <% end %>
-
-
- """
- end
-
- defp medal_class(1), do: "bg-amber-500/20 border-amber-700/50"
- defp medal_class(2), do: "bg-slate-700/50 border-slate-500/50"
- defp medal_class(3), do: "bg-orange-700/20 border-orange-700/50"
-
- defp get_weekly_rankings do
- transactions_query =
- from t in Transaction,
- join: u in Accounts.User,
- on: t.user_id == u.id,
- where: not is_nil(u.country) and not is_nil(t.succeeded_at),
- group_by: [
- u.id,
- u.country,
- u.handle,
- u.avatar_url,
- fragment("date_trunc('week', ?::timestamp)", t.succeeded_at)
- ],
- select: %{
- user_id: u.id,
- handle: u.handle,
- avatar_url: u.avatar_url,
- country: u.country,
- week: fragment("date_trunc('week', ?::timestamp)", t.succeeded_at),
- total_earned: sum(t.net_amount)
- }
-
- transactions = Repo.all(transactions_query)
-
- transactions
- |> Enum.group_by(& &1.week)
- |> Enum.map(fn {week, entries} ->
- rankings =
- entries
- |> Enum.group_by(&Regions.get_region(&1.country))
- |> Enum.reject(fn {region, _} -> is_nil(region) end)
- |> Enum.map(fn {region, region_entries} ->
- total =
- Enum.reduce(region_entries, Money.zero(:USD), fn entry, acc ->
- Money.add!(acc, entry.total_earned)
- end)
-
- top_earners =
- region_entries
- |> Enum.sort_by(& &1.total_earned, :desc)
- |> Enum.take(5)
-
- {region, total, top_earners}
- end)
- |> Enum.sort_by(fn {_, total, _} -> total end, :desc)
-
- {week, rankings}
- end)
- |> Enum.reject(fn {week, _} -> is_nil(week) end)
- |> Enum.sort_by(fn {week, _} -> week end, {:desc, NaiveDateTime})
- end
-
- defp calculate_medals(weeks) do
- Enum.reduce(weeks, %{}, fn {_week, rankings}, acc ->
- rankings
- |> Enum.take(3)
- |> Enum.with_index(1)
- |> Enum.reduce(acc, fn {{region, _total, _top_earners}, position}, region_acc ->
- region_data = Map.get(region_acc, region, %{gold: 0, silver: 0, bronze: 0, total: 0})
-
- updated_data =
- case position do
- 1 -> Map.update!(region_data, :gold, &(&1 + 1))
- 2 -> Map.update!(region_data, :silver, &(&1 + 1))
- 3 -> Map.update!(region_data, :bronze, &(&1 + 1))
- end
-
- updated_data =
- Map.put(
- updated_data,
- :total,
- updated_data.gold * 3 + updated_data.silver * 2 + updated_data.bronze
- )
-
- Map.put(region_acc, region, updated_data)
- end)
- end)
- end
-end
diff --git a/lib/algora_web/router.ex b/lib/algora_web/router.ex
index 623cc86c9..cd906f9df 100644
--- a/lib/algora_web/router.ex
+++ b/lib/algora_web/router.ex
@@ -50,17 +50,10 @@ defmodule AlgoraWeb.Router do
on_mount: [{AlgoraWeb.UserAuth, :ensure_admin}, AlgoraWeb.User.Nav]
end
- live_session :community,
- layout: {AlgoraWeb.Layouts, :user},
- on_mount: [{AlgoraWeb.UserAuth, :ensure_authenticated}, AlgoraWeb.User.Nav] do
- live "/home", User.DashboardLive, :index
- end
-
live_session :authenticated,
layout: {AlgoraWeb.Layouts, :user},
on_mount: [{AlgoraWeb.UserAuth, :ensure_authenticated}, AlgoraWeb.User.Nav] do
- # live "/dashboard", User.DashboardLive, :index
- live "/dashboard", Community.DashboardLive, :index
+ live "/home", User.DashboardLive, :index
live "/bounties", BountiesLive, :index
live "/community", CommunityLive, :index
live "/user/transactions", User.TransactionsLive, :index
@@ -73,15 +66,8 @@ defmodule AlgoraWeb.Router do
on_mount: [{AlgoraWeb.UserAuth, :current_user}, AlgoraWeb.Org.Nav] do
live "/org/:org_handle", Org.DashboardLive, :index
live "/org/:org_handle/home", Org.DashboardPublicLive, :index
- live "/org/:org_handle/bounties/new", Org.CreateBountyLive, :new
- live "/org/:org_handle/jobs/new", Org.CreateJobLive, :new
live "/org/:org_handle/bounties", Org.BountiesLive, :index
live "/org/:org_handle/contracts/:id", Contract.ViewLive
- live "/org/:org_handle/projects", Project.IndexLive, :index
- # live "/org/:org_handle/projects/:id", Project.ViewLive
- live "/org/:org_handle/jobs", Org.JobsLive, :index
- live "/org/:org_handle/jobs/:id", Org.JobLive, :index
- live "/org/:org_handle/chat", ChatLive, :index
live "/org/:org_handle/team", Org.TeamLive, :index
live "/org/:org_handle/leaderboard", Org.LeaderboardLive, :index
end
@@ -98,11 +84,6 @@ defmodule AlgoraWeb.Router do
live "/org/:org_handle/transactions", Org.TransactionsLive, :index
end
- live_session :org2,
- on_mount: [{AlgoraWeb.UserAuth, :current_user}, AlgoraWeb.Org.Nav] do
- live "/org/:org_handle/projects/:id", DevLive
- end
-
live_session :default, on_mount: [{AlgoraWeb.UserAuth, :current_user}] do
live "/auth/login", SignInLive, :index
live "/payment/success", Payment.SuccessLive, :index
@@ -111,34 +92,15 @@ defmodule AlgoraWeb.Router do
live "/claims/:group_id", ClaimLive
end
- live "/orgs/new", Org.CreateLive
-
- live "/projects/new", Project.CreateLive
- live "/projects", Project.IndexLive
- live "/projects/:id", Project.ViewLive
-
- live "/jobs/new", Job.CreateLive
- live "/jobs", Job.IndexLive
- live "/jobs/:id", Job.ViewLive
-
live "/leaderboard", LeaderboardLive
live_session :onboarding,
on_mount: [{AlgoraWeb.VisitorCountry, :current_country}] do
live "/onboarding/org", Onboarding.OrgLive
live "/onboarding/dev", Onboarding.DevLive
- live "/companies", CompaniesLive, :index
- live "/developers", DevelopersLive, :index
live "/pricing", PricingLive
end
- live_session :tmp,
- on_mount: [{AlgoraWeb.VisitorCountry, :current_country}] do
- live "/tmp/pricing", Tmp.PricingLive
- end
-
- live "/trotw", TROTWLive
-
live "/open-source", OpenSourceLive, :index
live_session :root,
diff --git a/priv/repo/migrations/20250311110634_drop_activities_tables.exs b/priv/repo/migrations/20250311110634_drop_activities_tables.exs
new file mode 100644
index 000000000..414e68647
--- /dev/null
+++ b/priv/repo/migrations/20250311110634_drop_activities_tables.exs
@@ -0,0 +1,9 @@
+defmodule Algora.Repo.Migrations.DropActivitiesTables do
+ use Ecto.Migration
+
+ def change do
+ drop table(:job_activities)
+ drop table(:application_activities)
+ drop table(:project_activities)
+ end
+end
diff --git a/test/algora/accounts_test.exs b/test/algora/accounts_test.exs
index 614b2ec15..d02f69ddc 100644
--- a/test/algora/accounts_test.exs
+++ b/test/algora/accounts_test.exs
@@ -2,6 +2,7 @@ defmodule Algora.AccountsTest do
use Algora.DataCase
alias Algora.Accounts
+ alias Algora.Organizations
describe "accounts" do
test "register github user" do
@@ -30,7 +31,7 @@ defmodule Algora.AccountsTest do
assert [sort_by_tech_stack: ["rust"]] |> Accounts.fetch_developer_by() |> elem(1) |> Map.get(:id) == user_2.id
assert [] |> Accounts.list_developers() |> length() == 2
- assert [] |> Accounts.list_orgs() |> length() == 1
+ assert [] |> Organizations.list_orgs() |> length() == 1
assert_activity_names_for_user(user_1.id, [])
assert_activity_names_for_user(org_1.id, [])