Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
5beea61
init bounty page
zcesur Mar 2, 2025
327705b
Merge branch 'main' of github.com:algora-io/console into feat/exclusi…
zcesur Mar 31, 2025
9f28d01
Merge branch 'main' of github.com:algora-io/console into feat/exclusi…
zcesur Mar 31, 2025
70a2b0f
feat: add exclusive bounty sharing functionality
zcesur Mar 31, 2025
849a7c3
Merge branch 'main' of github.com:algora-io/console into feat/exclusi…
zcesur Mar 31, 2025
60a4eb4
feat: implement exclusive sharing logic in BountyLive
zcesur Mar 31, 2025
2ed1d50
feat: enhance bounty sharing UI with social media options
zcesur Mar 31, 2025
5ea2457
update layout to make space for chat
zcesur Mar 31, 2025
9c3a6a0
add chatbox
zcesur Mar 31, 2025
4c8c520
refactor: update chat section layout in BountyLive
zcesur Mar 31, 2025
e0986e5
refactor: improve layout and styling in BountyLive
zcesur Mar 31, 2025
83f9332
refactor: improve exclusives and social sharing layout in BountyLive
zcesur Mar 31, 2025
6ab55c7
refactor: simplify bounty display in Bounties component
zcesur Mar 31, 2025
d7c3c12
add missing alias
zcesur Apr 1, 2025
21ea058
refactor: enhance bounty display styling in BountyLive
zcesur Apr 1, 2025
d307d3d
feat: add user retrieval by provider ID in Workspace
zcesur Apr 1, 2025
4b447f6
refactor: update styling and layout in BountyLive
zcesur Apr 1, 2025
e2d1c3f
feat: add deadline field to bounties and implement validation
zcesur Apr 1, 2025
a6be136
feat: enhance chat functionality and thread management
zcesur Apr 1, 2025
6e194b7
feat: update BountyLive layout and user display
zcesur Apr 1, 2025
f5f68f5
feat: enhance user avatar display in BountyLive
zcesur Apr 1, 2025
399f574
feat: add avatar_group component for enhanced participant display
zcesur Apr 1, 2025
7b2faeb
fix: update avatar_group component to increase limit and enhance styling
zcesur Apr 1, 2025
3dc92a8
feat: enhance bounty payment process in BountyLive
zcesur Apr 1, 2025
a0bba53
refactor: update amount field type and validation in BountyLive
zcesur Apr 1, 2025
3f7ea04
fix: update show_reward_modal assignment in BountyLive
zcesur Apr 1, 2025
1d19562
feat: add repository navigation and enhance BountyLive functionality
zcesur Apr 1, 2025
d5ad53f
feat: configure max_age for OGImageController in development and prod…
zcesur Apr 1, 2025
2ddc4f9
fix: deadline input
zcesur Apr 1, 2025
78c7b7c
swap cols
zcesur Apr 1, 2025
05c9e42
fix max_age
zcesur Apr 1, 2025
c3b874e
minor fixes
zcesur Apr 1, 2025
b515dad
improvements
zcesur Apr 1, 2025
d2bb4e1
refactor: enhance BountyLive mount and render logic
zcesur Apr 1, 2025
3bfddca
refactor: improve layout and styling in BountyLive component
zcesur Apr 1, 2025
6c6a1dc
refactor
zcesur Apr 1, 2025
7d51995
refactor: update styling and layout in BountyLive component
zcesur Apr 1, 2025
d1aec5c
refactor: enhance link styling and accessibility in BountyLive component
zcesur Apr 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,5 @@ config :algora,
plausible_url: System.get_env("PLAUSIBLE_URL"),
assets_url: System.get_env("ASSETS_URL"),
ingest_url: System.get_env("INGEST_URL")

config :algora, AlgoraWeb.OGImageController, max_age: 0
2 changes: 2 additions & 0 deletions config/prod.exs
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,7 @@ config :algora,
assets_url: System.get_env("ASSETS_URL"),
ingest_url: System.get_env("INGEST_URL")

config :algora, AlgoraWeb.OGImageController, max_age: 600

# Runtime production configuration, including reading
# of environment variables, is done on config/runtime.exs.
1 change: 1 addition & 0 deletions lib/algora/accounts/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ defmodule Algora.Accounts do
type: u.type,
id: u.id,
handle: u.handle,
provider_login: u.provider_login,
name: u.name,
provider_login: u.provider_login,
provider_meta: u.provider_meta,
Expand Down
52 changes: 52 additions & 0 deletions lib/algora/admin/admin.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ defmodule Algora.Admin do
@moduledoc false
import Ecto.Query

alias Algora.Accounts
alias Algora.Accounts.User
alias Algora.Activities.SendDiscord
alias Algora.Bounties.Claim
Expand All @@ -17,6 +18,57 @@ defmodule Algora.Admin do

require Logger

def seed do
import Algora.Factory

title = "Monthly PR Review & Triage Bounty"

description = """
## What needs doing

Review open pull requests across our repos and:

1. Test them locally
2. Leave helpful comments
3. Merge or close stale PRs
4. Tag relevant people when needed

## Success looks like

- [ ] All PRs older than 2 weeks have been reviewed
- [ ] Clear comments left on PRs needing changes
- [ ] Stale PRs (>30 days old) closed with explanation
- [ ] Weekly summary posted in #dev channel
"""

org = Repo.get_by!(User, handle: "piedpiper0")
user = Repo.get_by!(User, handle: "zcesur")

repository = insert!(:repository, user: org)
ticket = insert!(:ticket, title: title, repository: repository, description: description)
bounty = insert!(:bounty, ticket: ticket, owner: org, creator: user, amount: Money.new(1000, :USD))

for contributor <- Accounts.list_featured_developers() do
insert!(:transaction,
type: :debit,
net_amount: Money.new(1000, :USD),
user_id: org.id,
bounty_id: bounty.id,
status: :succeeded
)

insert!(:transaction,
type: :credit,
net_amount: Money.new(1000, :USD),
user_id: contributor.id,
bounty_id: bounty.id,
status: :succeeded
)
end

IO.puts("#{AlgoraWeb.Endpoint.url()}/org/#{org.handle}/bounties/#{bounty.id}")
end

def magic(:email, email, return_to),
do: AlgoraWeb.Endpoint.url() <> AlgoraWeb.UserAuth.generate_login_path(email, return_to)

Expand Down
11 changes: 8 additions & 3 deletions lib/algora/bounties/bounties.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ defmodule Algora.Bounties do
def base_query, do: Bounty

@type criterion ::
{:limit, non_neg_integer() | :infinity}
{:id, String.t()}
| {:limit, non_neg_integer() | :infinity}
| {:ticket_id, String.t()}
| {:owner_id, String.t()}
| {:status, :open | :paid}
Expand Down Expand Up @@ -695,15 +696,16 @@ defmodule Algora.Bounties do
bounty_id: String.t(),
claims: [Claim.t()]
},
opts :: [ticket_ref: %{owner: String.t(), repo: String.t(), number: integer()}]
opts :: [ticket_ref: %{owner: String.t(), repo: String.t(), number: integer()}, recipient: User.t()]
) ::
{:ok, String.t()} | {:error, atom()}
def reward_bounty(%{owner: owner, amount: amount, bounty_id: bounty_id, claims: claims}, opts \\ []) do
create_payment_session(
%{owner: owner, amount: amount, description: "Bounty payment for OSS contributions"},
ticket_ref: opts[:ticket_ref],
bounty_id: bounty_id,
claims: claims
claims: claims,
recipient: opts[:recipient]
)
end

Expand Down Expand Up @@ -997,6 +999,9 @@ defmodule Algora.Bounties do
@spec apply_criteria(Ecto.Queryable.t(), [criterion()]) :: Ecto.Queryable.t()
defp apply_criteria(query, criteria) do
Enum.reduce(criteria, query, fn
{:id, id}, query ->
from([b] in query, where: b.id == ^id)

{:limit, :infinity}, query ->
query

Expand Down
8 changes: 8 additions & 0 deletions lib/algora/bounties/schemas/bounty.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ defmodule Algora.Bounties.Bounty do
field :autopay_disabled, :boolean, default: false
field :visibility, Ecto.Enum, values: [:community, :exclusive, :public], null: false, default: :community
field :shared_with, {:array, :string}, null: false, default: []
field :deadline, :utc_datetime_usec

belongs_to :ticket, Algora.Workspace.Ticket
belongs_to :owner, User
Expand Down Expand Up @@ -42,6 +43,13 @@ defmodule Algora.Bounties.Bounty do
|> Algora.Validations.validate_money_positive(:amount)
end

def settings_changeset(bounty, attrs) do
bounty
|> cast(attrs, [:visibility, :shared_with, :deadline])
|> Algora.Validations.validate_date_in_future(:deadline)
|> validate_required([:visibility, :shared_with])
end

def url(%{repository: %{name: name, owner: %{login: login}}, ticket: %{provider: "github", number: number}}) do
"https://github.com/#{login}/#{name}/issues/#{number}"
end
Expand Down
77 changes: 60 additions & 17 deletions lib/algora/chat/chat.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ defmodule Algora.Chat do
alias Algora.Chat.Thread
alias Algora.Repo

def broadcast(message) do
Phoenix.PubSub.broadcast(Algora.PubSub, "chat:thread:#{message.thread_id}", message)
defmodule MessageCreated do
@moduledoc false
defstruct message: nil, participant: nil
end

def broadcast(%MessageCreated{} = event) do
Phoenix.PubSub.broadcast(Algora.PubSub, "chat:thread:#{event.message.thread_id}", event)
end

def subscribe(thread_id) do
Expand All @@ -23,7 +28,6 @@ defmodule Algora.Chat do
|> Thread.changeset(%{title: "#{User.handle(user_1)} <> #{User.handle(user_2)}"})
|> Repo.insert()

# Add participants
for user <- [user_1, user_2] do
%Participant{}
|> Participant.changeset(%{
Expand All @@ -46,7 +50,7 @@ defmodule Algora.Chat do
|> Repo.insert()

participants = Enum.uniq_by([user | admins], & &1.id)
# Add participants

for u <- participants do
%Participant{}
|> Participant.changeset(%{
Expand All @@ -61,20 +65,41 @@ defmodule Algora.Chat do
end)
end

defp ensure_participant(thread_id, user_id) do
case Repo.fetch_by(Participant, thread_id: thread_id, user_id: user_id) do
{:ok, participant} ->
{:ok, participant}

{:error, _} ->
%Participant{}
|> Participant.changeset(%{
thread_id: thread_id,
user_id: user_id,
last_read_at: DateTime.utc_now()
})
|> Repo.insert()
end
end

defp insert_message(thread_id, sender_id, content) do
%Message{}
|> Message.changeset(%{
thread_id: thread_id,
sender_id: sender_id,
content: content
})
|> Repo.insert()
end

def send_message(thread_id, sender_id, content) do
case %Message{}
|> Message.changeset(%{
thread_id: thread_id,
sender_id: sender_id,
content: content
})
|> Repo.insert() do
{:ok, message} ->
message |> Repo.preload(:sender) |> broadcast()
{:ok, message}

{:error, changeset} ->
{:error, changeset}
with {:ok, participant} <- ensure_participant(thread_id, sender_id),
{:ok, message} <- insert_message(thread_id, sender_id, content) do
broadcast(%MessageCreated{
message: Repo.preload(message, :sender),
participant: Repo.preload(participant, :user)
})

{:ok, message}
end
end

Expand Down Expand Up @@ -107,6 +132,12 @@ defmodule Algora.Chat do
|> Repo.all()
end

def list_participants(thread_id) do
Participant
|> where(thread_id: ^thread_id)
|> Repo.all()
end

def mark_as_read(thread_id, user_id) do
Participant
|> where(thread_id: ^thread_id, user_id: ^user_id)
Expand Down Expand Up @@ -145,4 +176,16 @@ defmodule Algora.Chat do
thread -> {:ok, thread}
end
end

def get_or_create_bounty_thread(bounty) do
case Repo.fetch_by(Thread, bounty_id: bounty.id) do
{:ok, thread} ->
{:ok, thread}

{:error, _} ->
%Thread{}
|> Thread.changeset(%{title: "Contributor chat", bounty_id: bounty.id})
|> Repo.insert()
end
end
end
5 changes: 3 additions & 2 deletions lib/algora/chat/schemas/thread.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ defmodule Algora.Chat.Thread do

typed_schema "threads" do
field :title, :string

field :bounty_id, :string
has_many :messages, Algora.Chat.Message
has_many :participants, Algora.Chat.Participant
has_many :activities, {"thread_activities", Activity}, foreign_key: :assoc_id
Expand All @@ -16,8 +16,9 @@ defmodule Algora.Chat.Thread do

def changeset(thread, attrs) do
thread
|> cast(attrs, [:title])
|> cast(attrs, [:title, :bounty_id])
|> validate_required([:title])
|> generate_id()
|> unique_constraint(:bounty_id)
end
end
10 changes: 10 additions & 0 deletions lib/algora/shared/validations.ex
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,14 @@ defmodule Algora.Validations do
_ -> changeset
end
end

def validate_date_in_future(changeset, field) do
validate_change(changeset, field, fn _, date ->
if date && Date.before?(date, DateTime.utc_now()) do
[{field, "must be in the future"}]
else
[]
end
end)
end
end
7 changes: 7 additions & 0 deletions lib/algora/workspace/workspace.ex
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,13 @@ defmodule Algora.Workspace do
end
end

def ensure_user_by_provider_id(token, provider_id) do
case Repo.get_by(User, provider: "github", provider_id: provider_id) do
%User{} = user -> {:ok, user}
nil -> create_user_from_github(token, provider_id)
end
end

def sync_user(user, repository, owner, repo) do
github_user = repository["owner"]

Expand Down
4 changes: 0 additions & 4 deletions lib/algora_web/components/bounties.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@ defmodule AlgoraWeb.Components.Bounties do
<div class="flex-grow min-w-0 mr-4">
<div class="flex items-center text-sm">
<span class="font-semibold mr-1">{bounty.repository.owner.name}</span>
<.icon name="tabler-chevron-right" class="mr-1 size-3 text-muted-foreground" />
<span class="font-semibold mr-1 text-muted-foreground">
{bounty.repository.name}
</span>
<span class="text-muted-foreground mr-2">#{bounty.ticket.number}</span>
<span class="font-display whitespace-nowrap text-sm font-semibold tabular-nums text-success mr-2">
{Money.to_string!(bounty.amount)}
Expand Down
1 change: 1 addition & 0 deletions lib/algora_web/components/core_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1288,6 +1288,7 @@ defmodule AlgoraWeb.CoreComponents do
defdelegate alert_title(assigns), to: Alert
defdelegate alert(assigns), to: Alert
defdelegate avatar_fallback(assigns), to: Avatar
defdelegate avatar_group(assigns), to: Avatar
defdelegate avatar_image(assigns), to: Avatar
defdelegate avatar(assigns), to: Avatar
defdelegate badge(assigns), to: AlgoraWeb.Components.UI.Badge
Expand Down
29 changes: 29 additions & 0 deletions lib/algora_web/components/ui/avatar.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ defmodule AlgoraWeb.Components.UI.Avatar do
attr :class, :string, default: nil
attr :rest, :global

slot :inner_block, required: true

def avatar(assigns) do
~H"""
<div class={classes(["relative h-10 w-10 shrink-0 overflow-hidden rounded-full", @class])} {@rest}>
Expand Down Expand Up @@ -48,4 +50,31 @@ defmodule AlgoraWeb.Components.UI.Avatar do
</span>
"""
end

attr :class, :string, default: nil
attr :rest, :global
attr :srcs, :list, default: []
attr :limit, :integer, default: 4

def avatar_group(assigns) do
~H"""
<div class="relative flex -space-x-1">
<%= for src <- @srcs |> Enum.take(@limit) do %>
<.avatar class={classes(["ring-4 ring-background", @class])}>
<.avatar_image src={src} />
<.avatar_fallback>
{Algora.Util.initials(src)}
</.avatar_fallback>
</.avatar>
<% end %>
<%= if length(@srcs) > @limit do %>
<.avatar class={classes(["ring-4 ring-background", @class])}>
<.avatar_fallback>
+{length(@srcs) - @limit}
</.avatar_fallback>
</.avatar>
<% end %>
</div>
"""
end
end
Loading