diff --git a/config/config.exs b/config/config.exs
index 7555d7bdd..b0e68564e 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -23,6 +23,7 @@ config :algora,
     {"/create/org", "/onboarding/org"},
     {"/solve", "/onboarding/dev"},
     {"/onboarding/solver", "/onboarding/dev"},
+    {"/:org/contract/:id", "/:org/contracts/:id"},
     {"/org/*path", "/*path"},
     {"/@/:handle", "/:handle/profile"}
   ]
diff --git a/lib/algora/bounties/bounties.ex b/lib/algora/bounties/bounties.ex
index 85b5f22fa..929929bf4 100644
--- a/lib/algora/bounties/bounties.ex
+++ b/lib/algora/bounties/bounties.ex
@@ -51,7 +51,8 @@ defmodule Algora.Bounties do
           amount: Money.t(),
           ticket: Ticket.t(),
           visibility: Bounty.visibility(),
-          shared_with: [String.t()]
+          shared_with: [String.t()],
+          hours_per_week: integer() | nil
         }) ::
           {:ok, Bounty.t()} | {:error, atom()}
   defp do_create_bounty(%{creator: creator, owner: owner, amount: amount, ticket: ticket} = params) do
@@ -62,7 +63,8 @@ defmodule Algora.Bounties do
         owner_id: owner.id,
         creator_id: creator.id,
         visibility: params[:visibility] || owner.bounty_mode,
-        shared_with: params[:shared_with] || []
+        shared_with: params[:shared_with] || [],
+        hours_per_week: params[:hours_per_week]
       })
 
     changeset
@@ -109,7 +111,8 @@ defmodule Algora.Bounties do
             command_id: integer(),
             command_source: :ticket | :comment,
             visibility: Bounty.visibility() | nil,
-            shared_with: [String.t()] | nil
+            shared_with: [String.t()] | nil,
+            hours_per_week: integer() | nil
           ]
         ) ::
           {:ok, Bounty.t()} | {:error, atom()}
@@ -140,7 +143,8 @@ defmodule Algora.Bounties do
                     amount: amount,
                     ticket: ticket,
                     visibility: opts[:visibility],
-                    shared_with: shared_with
+                    shared_with: shared_with,
+                    hours_per_week: opts[:hours_per_week]
                   })
 
                 :set ->
@@ -190,7 +194,8 @@ defmodule Algora.Bounties do
           opts :: [
             strategy: strategy(),
             visibility: Bounty.visibility() | nil,
-            shared_with: [String.t()] | nil
+            shared_with: [String.t()] | nil,
+            hours_per_week: integer() | nil
           ]
         ) ::
           {:ok, Bounty.t()} | {:error, atom()}
@@ -209,7 +214,8 @@ defmodule Algora.Bounties do
                amount: amount,
                ticket: ticket,
                visibility: opts[:visibility],
-               shared_with: shared_with
+               shared_with: shared_with,
+               hours_per_week: opts[:hours_per_week]
              }),
            {:ok, _job} <- notify_bounty(%{owner: owner, bounty: bounty}) do
         broadcast()
@@ -862,6 +868,7 @@ defmodule Algora.Bounties do
              initialize_charge(%{
                id: Nanoid.generate(),
                user_id: owner.id,
+               bounty_id: opts[:bounty_id],
                gross_amount: gross_amount,
                net_amount: amount,
                total_fee: Money.sub!(gross_amount, amount),
@@ -961,16 +968,18 @@ defmodule Algora.Bounties do
     end)
   end
 
-  defp initialize_charge(%{
-         id: id,
-         user_id: user_id,
-         gross_amount: gross_amount,
-         net_amount: net_amount,
-         total_fee: total_fee,
-         line_items: line_items,
-         group_id: group_id,
-         idempotency_key: idempotency_key
-       }) do
+  defp initialize_charge(
+         %{
+           id: id,
+           user_id: user_id,
+           gross_amount: gross_amount,
+           net_amount: net_amount,
+           total_fee: total_fee,
+           line_items: line_items,
+           group_id: group_id,
+           idempotency_key: idempotency_key
+         } = params
+       ) do
     %Transaction{}
     |> change(%{
       id: id,
@@ -978,6 +987,7 @@ defmodule Algora.Bounties do
       type: :charge,
       status: :initialized,
       user_id: user_id,
+      bounty_id: params[:bounty_id],
       gross_amount: gross_amount,
       net_amount: net_amount,
       total_fee: total_fee,
diff --git a/lib/algora/bounties/schemas/bounty.ex b/lib/algora/bounties/schemas/bounty.ex
index b655e17ba..c85aadf29 100644
--- a/lib/algora/bounties/schemas/bounty.ex
+++ b/lib/algora/bounties/schemas/bounty.ex
@@ -15,6 +15,7 @@ defmodule Algora.Bounties.Bounty do
     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
+    field :hours_per_week, :integer
 
     belongs_to :ticket, Algora.Workspace.Ticket
     belongs_to :owner, User
@@ -33,7 +34,7 @@ defmodule Algora.Bounties.Bounty do
 
   def changeset(bounty, attrs) do
     bounty
-    |> cast(attrs, [:amount, :ticket_id, :owner_id, :creator_id, :visibility, :shared_with])
+    |> cast(attrs, [:amount, :ticket_id, :owner_id, :creator_id, :visibility, :shared_with, :hours_per_week])
     |> validate_required([:amount, :ticket_id, :owner_id, :creator_id])
     |> generate_id()
     |> foreign_key_constraint(:ticket)
@@ -67,6 +68,8 @@ defmodule Algora.Bounties.Bounty do
     Algora.Util.path_from_url(url)
   end
 
+  def path(_bounty), do: nil
+
   def full_path(%{repository: %{name: name, owner: %{login: login}}, ticket: %{number: number}}) do
     "#{login}/#{name}##{number}"
   end
diff --git a/lib/algora/organizations/schemas/member.ex b/lib/algora/organizations/schemas/member.ex
index 1891a8479..8913a32c4 100644
--- a/lib/algora/organizations/schemas/member.ex
+++ b/lib/algora/organizations/schemas/member.ex
@@ -36,4 +36,6 @@ defmodule Algora.Organizations.Member do
   end
 
   def can_create_bounty?(role), do: role in [:admin, :mod]
+
+  def can_create_contract?(role), do: role in [:admin, :mod]
 end
diff --git a/lib/algora/shared/validations.ex b/lib/algora/shared/validations.ex
index 1e57806b0..7ea45497e 100644
--- a/lib/algora/shared/validations.ex
+++ b/lib/algora/shared/validations.ex
@@ -40,6 +40,33 @@ defmodule Algora.Validations do
     end
   end
 
+  def validate_github_handle(changeset, field, embed_field \\ nil) do
+    case get_change(changeset, field) do
+      handle when not is_nil(handle) ->
+        # Check if user is already embedded with matching provider_login
+        existing_user = embed_field && get_field(changeset, embed_field)
+
+        if existing_user && existing_user.provider_login == handle do
+          changeset
+        else
+          case Algora.Workspace.ensure_user(Algora.Admin.token!(), handle) do
+            {:ok, user} ->
+              if embed_field do
+                put_embed(changeset, embed_field, user)
+              else
+                changeset
+              end
+
+            {:error, error, _, _, _, _} ->
+              add_error(changeset, field, error)
+          end
+        end
+
+      _ ->
+        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
diff --git a/lib/algora_web/components/layouts/user.html.heex b/lib/algora_web/components/layouts/user.html.heex
index b4785238c..62d12ab0b 100644
--- a/lib/algora_web/components/layouts/user.html.heex
+++ b/lib/algora_web/components/layouts/user.html.heex
@@ -135,35 +135,6 @@
         
       <% end %>
     
-    <%= if main_bounty_form = Map.get(assigns, :main_bounty_form) do %>
-      
-        <.button
-          phx-click="open_main_bounty_form"
-          class="h-9 w-9 rounded-md flex items-center justify-center relative"
-        >
-          <.icon name="tabler-diamond" class="h-6 w-6 shrink-0" />
-          <.icon
-            name="tabler-plus"
-            class="h-[0.8rem] w-[0.8rem] shrink-0 absolute bottom-[0.2rem] right-[0.2rem]"
-          />
-        
-        <.drawer
-          show={@main_bounty_form_open?}
-          direction="right"
-          on_cancel="close_main_bounty_form"
-        >
-          <.drawer_header>
-            <.drawer_title>Create new bounty
-            <.drawer_description>
-              Create and fund a bounty for an issue
-            
-          
-          <.drawer_content class="mt-4">
-            
-          
-        
-      
@@ -237,6 +208,60 @@
           
         
         <%= if @current_user do %>
+          <%= if main_contract_form = Map.get(assigns, :main_contract_form) do %>
+            
+              <.button
+                phx-click="open_main_contract_form"
+                class="h-9 w-9 rounded-md flex items-center justify-center relative"
+              >
+                <.icon name="tabler-user-dollar" class="h-6 w-6 shrink-0" />
+              
+              <.drawer
+                show={@main_contract_form_open?}
+                direction="right"
+                on_cancel="close_main_contract_form"
+              >
+                <.drawer_header>
+                  <.drawer_title>Create new contract
+                  <.drawer_description>
+                    Engage a developer for ongoing work
+                  
+                
+                <.drawer_content class="mt-4">
+                  
+                
+              
+            
+              <.button
+                phx-click="open_main_bounty_form"
+                class="h-9 w-9 rounded-md flex items-center justify-center relative"
+              >
+                <.icon name="tabler-diamond" class="h-6 w-6 shrink-0" />
+                <.icon
+                  name="tabler-plus"
+                  class="h-[0.8rem] w-[0.8rem] shrink-0 absolute bottom-[0.2rem] right-[0.2rem]"
+                />
+              
+              <.drawer
+                show={@main_bounty_form_open?}
+                direction="right"
+                on_cancel="close_main_bounty_form"
+              >
+                <.drawer_header>
+                  <.drawer_title>Create new bounty
+                  <.drawer_description>
+                    Create and fund a bounty for an issue
+                  
+                
+                <.drawer_content class="mt-4">
+                  
+                
+              
+            
+        <.input label="Title" field={@form[:title]} />
+        <.input label="Description (optional)" field={@form[:description]} type="textarea" />
+        
+          
+          
+            <%= for {label, value} <- type_options() do %>
+              
+            <% end %>
+          
+        
+
+        
+          <.input label="Amount" icon="tabler-currency-dollar" field={@form[:amount]} />
+        
+        
+          
+            <.input label="Hourly rate" icon="tabler-currency-dollar" field={@form[:hourly_rate]} />
+            <.input label="Hours per week" field={@form[:hours_per_week]} />
+          
+        
+
+        
+          <.input
+            label="GitHub handle"
+            field={@form[:contractor_handle]}
+            phx-debounce="500"
+            class="pl-10"
+          />
+          
+            <.avatar :if={get_field(@form.source, :contractor)} class="h-7 w-7">
+              <.avatar_image src={get_field(@form.source, :contractor).avatar_url} />
+            
+            <.icon name="github" class="h-7 w-7 text-muted-foreground" />
+          
+        
+      
+        <.button variant="secondary" phx-click="close_share_drawer" type="button">
+          Cancel
+        
+        <.button type="submit">
+          Share Contract <.icon name="tabler-arrow-right" class="-mr-1 ml-2 h-4 w-4" />
+        
+      
+    
+    """
   end
 end
diff --git a/lib/algora_web/live/contract_live.ex b/lib/algora_web/live/contract_live.ex
new file mode 100644
index 000000000..24030efa9
--- /dev/null
+++ b/lib/algora_web/live/contract_live.ex
@@ -0,0 +1,675 @@
+defmodule AlgoraWeb.ContractLive do
+  @moduledoc false
+  use AlgoraWeb, :live_view
+
+  import Ecto.Changeset
+  import Ecto.Query
+
+  alias Algora.Admin
+  alias Algora.Bounties
+  alias Algora.Bounties.Bounty
+  alias Algora.Bounties.LineItem
+  alias Algora.Chat
+  alias Algora.Organizations.Member
+  alias Algora.Payments
+  alias Algora.Repo
+  alias Algora.Util
+  alias Algora.Workspace
+
+  require Logger
+
+  defp tip_options, do: [{"None", 0}, {"10%", 10}, {"20%", 20}, {"50%", 50}]
+
+  defmodule RewardBountyForm do
+    @moduledoc false
+    use Ecto.Schema
+
+    import Ecto.Changeset
+
+    @primary_key false
+    embedded_schema do
+      field :amount, Algora.Types.USD
+      field :tip_percentage, :decimal
+    end
+
+    def changeset(form, attrs) do
+      form
+      |> cast(attrs, [:amount, :tip_percentage])
+      |> validate_required([:amount])
+      |> validate_number(:tip_percentage, greater_than_or_equal_to: 0)
+      |> Algora.Validations.validate_money_positive(:amount)
+    end
+  end
+
+  @impl true
+  def mount(%{"id" => bounty_id}, _session, socket) do
+    bounty =
+      Bounty
+      |> Repo.get!(bounty_id)
+      |> Repo.preload([:owner, :creator, :transactions, ticket: [repository: [:user]]])
+
+    timezone = if(params = get_connect_params(socket), do: params["timezone"])
+
+    {host, ticket_ref} =
+      if bounty.ticket.repository do
+        {bounty.ticket.repository.user,
+         %{
+           owner: bounty.ticket.repository.user.provider_login,
+           repo: bounty.ticket.repository.name,
+           number: bounty.ticket.number
+         }}
+      else
+        {bounty.owner, nil}
+      end
+
+    socket
+    |> assign(:bounty, bounty)
+    |> assign(:ticket_ref, ticket_ref)
+    |> assign(:host, host)
+    |> assign(:timezone, timezone)
+    |> on_mount(bounty)
+  end
+
+  @impl true
+  def mount(%{"repo_owner" => repo_owner, "repo_name" => repo_name, "number" => number}, _session, socket) do
+    number = String.to_integer(number)
+
+    ticket_ref = %{owner: repo_owner, repo: repo_name, number: number}
+
+    bounty =
+      from(b in Bounty,
+        join: t in assoc(b, :ticket),
+        join: r in assoc(t, :repository),
+        join: u in assoc(r, :user),
+        where: u.provider == "github",
+        where: u.provider_login == ^repo_owner,
+        where: r.name == ^repo_name,
+        where: t.number == ^number,
+        order_by: fragment("CASE WHEN ? = ? THEN 0 ELSE 1 END", u.id, ^socket.assigns.current_org.id),
+        limit: 1
+      )
+      |> Repo.one()
+      |> Repo.preload([:owner, :creator, :transactions, ticket: [repository: [:user]]])
+
+    socket
+    |> assign(:bounty, bounty)
+    |> assign(:ticket_ref, ticket_ref)
+    |> assign(:host, bounty.ticket.repository.user)
+    |> on_mount(bounty)
+  end
+
+  defp on_mount(socket, bounty) do
+    debits = Enum.filter(bounty.transactions, &(&1.type == :debit and &1.status == :succeeded))
+
+    total_paid =
+      debits
+      |> Enum.map(& &1.net_amount)
+      |> Enum.reduce(Money.zero(:USD, no_fraction_if_integer: true), &Money.add!(&1, &2))
+
+    ticket_body_html = Algora.Markdown.render(bounty.ticket.description)
+
+    reward_changeset =
+      RewardBountyForm.changeset(%RewardBountyForm{}, %{tip_percentage: 0})
+
+    {:ok, thread} = Chat.get_or_create_bounty_thread(bounty)
+    messages = thread.id |> Chat.list_messages() |> Repo.preload(:sender)
+    participants = thread.id |> Chat.list_participants() |> Repo.preload(:user)
+
+    if connected?(socket) do
+      Chat.subscribe(thread.id)
+    end
+
+    share_url =
+      if socket.assigns.ticket_ref do
+        url(
+          ~p"/#{socket.assigns.ticket_ref.owner}/#{socket.assigns.ticket_ref.repo}/issues/#{socket.assigns.ticket_ref.number}"
+        )
+      else
+        url(~p"/#{socket.assigns.bounty.owner.handle}/bounties/#{socket.assigns.bounty.id}")
+      end
+
+    {:ok,
+     socket
+     |> assign(:can_create_bounty, Member.can_create_bounty?(socket.assigns.current_user_role))
+     |> assign(:share_url, share_url)
+     |> assign(:page_title, bounty.ticket.title)
+     |> assign(:ticket, bounty.ticket)
+     |> assign(:total_paid, total_paid)
+     |> assign(:ticket_body_html, ticket_body_html)
+     |> assign(:show_reward_modal, false)
+     |> assign(:selected_context, nil)
+     |> assign(:line_items, [])
+     |> assign(:thread, thread)
+     |> assign(:messages, messages)
+     |> assign(:participants, participants)
+     |> assign(:reward_form, to_form(reward_changeset))
+     |> assign_contractor(bounty.shared_with)
+     |> assign_transactions()
+     |> assign_line_items()}
+  end
+
+  @impl true
+  def handle_params(_params, _url, %{assigns: %{current_user: nil}} = socket) do
+    {:noreply, socket}
+  end
+
+  @impl true
+  def handle_params(_params, _url, socket) do
+    {:noreply, socket}
+  end
+
+  @impl true
+  def handle_info(%Chat.MessageCreated{message: message, participant: participant}, socket) do
+    socket =
+      if message.id in Enum.map(socket.assigns.messages, & &1.id),
+        do: socket,
+        else: Phoenix.Component.update(socket, :messages, &(&1 ++ [message]))
+
+    socket =
+      if participant.id in Enum.map(socket.assigns.participants, & &1.id),
+        do: socket,
+        else: Phoenix.Component.update(socket, :participants, &(&1 ++ [participant]))
+
+    {:noreply, socket}
+  end
+
+  @impl true
+  def handle_event("send_message", %{"message" => content}, socket) do
+    {:ok, message} =
+      Chat.send_message(
+        socket.assigns.thread.id,
+        socket.assigns.current_user.id,
+        content
+      )
+
+    message = Repo.preload(message, :sender)
+
+    {:noreply,
+     socket
+     |> Phoenix.Component.update(:messages, &(&1 ++ [message]))
+     |> push_event("clear-input", %{selector: "#message-input"})}
+  end
+
+  @impl true
+  def handle_event("reward", _params, socket) do
+    {:noreply, assign(socket, :show_reward_modal, true)}
+  end
+
+  @impl true
+  def handle_event("close_drawer", _params, socket) do
+    {:noreply, close_drawers(socket)}
+  end
+
+  @impl true
+  def handle_event("validate_reward", %{"reward_bounty_form" => params}, socket) do
+    {:noreply,
+     socket
+     |> assign(:reward_form, to_form(RewardBountyForm.changeset(%RewardBountyForm{}, params)))
+     |> assign_line_items()}
+  end
+
+  @impl true
+  def handle_event("assign_line_items", %{"reward_bounty_form" => params}, socket) do
+    {:noreply, assign_line_items(socket)}
+  end
+
+  @impl true
+  def handle_event("pay_with_stripe", %{"reward_bounty_form" => params}, socket) do
+    changeset = RewardBountyForm.changeset(%RewardBountyForm{}, params)
+
+    case apply_action(changeset, :save) do
+      {:ok, _data} ->
+        case reward_bounty(socket, socket.assigns.bounty, changeset) do
+          {:ok, session_url} ->
+            {:noreply, redirect(socket, external: session_url)}
+
+          {:error, reason} ->
+            Logger.error("Failed to create payment session: #{inspect(reason)}")
+            {:noreply, put_flash(socket, :error, "Something went wrong")}
+        end
+
+      {:error, changeset} ->
+        {:noreply, assign(socket, :reward_form, to_form(changeset))}
+    end
+  end
+
+  @impl true
+  def handle_event(_event, _params, socket) do
+    {:noreply, socket}
+  end
+
+  @impl true
+  def render(assigns) do
+    ~H"""
+    
+      <.scroll_area class="xl:h-[calc(100svh-96px)] flex-1 pr-6">
+        
+          <.card>
+            <.card_content>
+              
+                
+                  
+                    <.avatar class="h-12 w-12 ring-2 ring-background">
+                      <.avatar_image src={@bounty.owner.avatar_url} />
+                      <.avatar_fallback>
+                        {Util.initials(@bounty.owner.name)}
+                      
+                    
+                    <.avatar class="h-12 w-12 ring-2 ring-background">
+                      <.avatar_image src={@contractor.avatar_url} />
+                      <.avatar_fallback>
+                        {Util.initials(@contractor.name)}
+                      
+                    
+                  
+                  
+                    
+                      {@bounty.ticket.title}
+                    
+                    
+                      Created {Calendar.strftime(@bounty.inserted_at, "%b %d, %Y")}
+                       0}
+                        class="space-x-2"
+                      >
+                        <.icon name="tabler-clock" class="h-4 w-4" />
+                        {@bounty.hours_per_week} hours per week
+                      
+                    
+                  
+                
+
+                
+                  
+                    {Money.to_string!(@bounty.amount)} 0}
+                      class="text-base"
+                    >
+                      /hr
+                    
+                  
+                  <.button :if={@can_create_bounty} phx-click="reward">
+                    Pay
+                  
+                
+              
+            
+          
+          <.card :if={@ticket_body_html}>
+            <.card_header>
+              <.card_title>
+                Description
+              
+            
+            <.card_content class="pt-0">
+              
+                {Phoenix.HTML.raw(@ticket_body_html)}
+              
+            
+          
+          <.card :if={length(@transactions) > 0}>
+            <.card_header>
+              <.card_title>
+                Timeline
+              
+            
+            <.card_content class="pt-0">
+              
+                
+                  
+                    
+                      
+                        | Date+ | +                          Description
++ | +                          Amount
++ | 
+                    
+                    
+                      <%= for transaction <- @transactions do %>
+                        
+                          | + +
+                              {Util.timestamp(transaction.inserted_at, @timezone)}
+                            + 
+                            + | +                            {description(transaction)}
++ | +                            <%= case transaction_direction(transaction.type) do %>
+                              <% :plus -> %>
+                                
+                                  {Money.to_string!(transaction.net_amount)}
+                                
+                              <% :minus -> %>
+                                
+                                  {Money.to_string!(transaction.net_amount)}
+                                
+                            <% end %>
++ | 
+                      <% end %>
+                    
+                  
+                
+              
+            
+          
+        
+      
+
+      
+        
+          
+            
+              <.avatar>
+                <.avatar_image src={@contractor.avatar_url} alt="Developer avatar" />
+                <.avatar_fallback>
+                  {Util.initials(@contractor.name)}
+                
+              
+              <%!-- 
+              
 --%>
+            
+            
+              
{@contractor.name}
+              
+                Active {Util.time_ago(@contractor.last_active_at)}
+              
+              
+                Offline
+              
+            
+          
+        
+        <.scroll_area
+          class="flex h-full flex-1 flex-col-reverse gap-6 p-4"
+          id="messages-container"
+          phx-hook="ScrollToBottom"
+        >
+          
+            <%= for {date, messages} <- @messages
+                |> Enum.group_by(fn msg ->
+                  case Date.diff(Date.utc_today(), DateTime.to_date(msg.inserted_at)) do
+                    0 -> "Today"
+                    1 -> "Yesterday"
+                    n when n <= 7 -> Calendar.strftime(msg.inserted_at, "%A")
+                    _ -> Calendar.strftime(msg.inserted_at, "%b %d")
+                  end
+                end)
+                |> Enum.sort_by(fn {_, msgs} -> hd(msgs).inserted_at end, Date) do %>
+              
+
+              
+                <%= for message <- Enum.sort_by(messages, & &1.inserted_at, Date) do %>
+                  
+                    <.avatar class="h-8 w-8">
+                      <.avatar_image src={message.sender.avatar_url} />
+                      <.avatar_fallback>
+                        {Util.initials(message.sender.name)}
+                      
+                    
+                    
+                      {message.content}
+                      
+                        {message.inserted_at
+                        |> DateTime.to_time()
+                        |> Time.to_string()
+                        |> String.slice(0..4)}
+                      
+                    
+                  
+                <% end %>
+              
+            <% end %>
+          
+        
+
+        
+      
+    
+            
+              <.card>
+                <.card_header>
+                  <.card_title>Payment Details
+                
+                <.card_content class="pt-0">
+                  
+                    <.input
+                      label="Amount"
+                      icon="tabler-currency-dollar"
+                      field={@reward_form[:amount]}
+                    />
+
+                    
+                      <.label>Tip
+                      
+                        <.radio_group
+                          class="grid grid-cols-4 gap-4"
+                          field={@reward_form[:tip_percentage]}
+                          options={tip_options()}
+                        />
+                      
+                    
+                  
+                
+              
+              <.card>
+                <.card_header>
+                  <.card_title>Payment Summary
+                
+                <.card_content class="pt-0">
+                  
+                    <%= for line_item <- @line_items do %>
+                      
+                        
- 
+                          <%= if line_item.image do %>
+                            <.avatar>
+                              <.avatar_image src={line_item.image} />
+                              <.avatar_fallback>
+                                {Util.initials(line_item.title)}
+                              
+                            
+                          <% else %>
+                            
+                          <% end %>
+                          
+                             {line_item.title} 
+                             {line_item.description} 
+                           
+                        
- 
+                          {Money.to_string!(line_item.amount)}
+                        
+                      
+                      
- 
+                        
+                        Total due +
+                      
- 
+                        {LineItem.gross_amount(@line_items)}
+                      
+                    
+                
+              
+            
+            
+              <.button variant="secondary" phx-click="close_drawer" type="button">
+                Cancel
+              
+              <.button type="submit">
+                Pay with Stripe <.icon name="tabler-arrow-right" class="-mr-1 ml-2 h-4 w-4" />
+              
+            
+          
+                        
                           <.icon name="tabler-chevron-right" class="h-4 w-4" />
                           <.link
                             href={
diff --git a/lib/algora_web/live/org/nav.ex b/lib/algora_web/live/org/nav.ex
index a9f875a58..494e80c8a 100644
--- a/lib/algora_web/live/org/nav.ex
+++ b/lib/algora_web/live/org/nav.ex
@@ -6,10 +6,12 @@ defmodule AlgoraWeb.Org.Nav do
   import Ecto.Changeset
   import Phoenix.LiveView
 
+  alias Algora.Accounts.User
   alias Algora.Bounties
   alias Algora.Organizations
   alias Algora.Organizations.Member
   alias AlgoraWeb.Forms.BountyForm
+  alias AlgoraWeb.Forms.ContractForm
   alias AlgoraWeb.OrgAuth
 
   require Logger
@@ -26,11 +28,18 @@ defmodule AlgoraWeb.Org.Nav do
           to_form(BountyForm.changeset(%BountyForm{}, %{}))
         end
 
+      main_contract_form =
+        if Member.can_create_contract?(current_user_role) do
+          to_form(ContractForm.changeset(%ContractForm{}, %{}))
+        end
+
       {:cont,
        socket
        |> assign(:screenshot?, not is_nil(params["screenshot"]))
        |> assign(:main_bounty_form, main_bounty_form)
        |> assign(:main_bounty_form_open?, false)
+       |> assign(:main_contract_form, main_contract_form)
+       |> assign(:main_contract_form_open?, false)
        |> assign(:current_org, current_org)
        |> assign(:current_user_role, current_user_role)
        |> assign(:nav, nav_items(current_org.handle, current_user_role))
@@ -105,6 +114,51 @@ defmodule AlgoraWeb.Org.Nav do
     end
   end
 
+  defp handle_event("validate_contract_main", %{"contract_form" => params}, socket) do
+    changeset = ContractForm.changeset(%ContractForm{}, params)
+    {:cont, assign(socket, :main_contract_form, to_form(changeset))}
+  end
+
+  defp handle_event("create_contract_main", %{"contract_form" => params}, socket) do
+    changeset = ContractForm.changeset(%ContractForm{}, params)
+
+    case apply_action(changeset, :save) do
+      {:ok, data} ->
+        amount =
+          case data.type do
+            :fixed -> data.amount
+            :hourly -> data.hourly_rate
+          end
+
+        bounty_res =
+          Bounties.create_bounty(
+            %{
+              creator: socket.assigns.current_user,
+              owner: socket.assigns.current_org,
+              amount: amount,
+              title: data.title,
+              description: data.description
+            },
+            hours_per_week: data.hours_per_week,
+            shared_with: [data.contractor.provider_id],
+            visibility: :exclusive
+          )
+
+        case bounty_res do
+          {:ok, bounty} ->
+            {:cont, redirect(socket, to: ~p"/#{socket.assigns.current_org.handle}/contracts/#{bounty.id}")}
+
+          {:error, reason} ->
+            Logger.error("Failed to create bounty: #{inspect(reason)}")
+            {:cont, put_flash(socket, :error, "Something went wrong")}
+        end
+
+      {:error, changeset} ->
+        Logger.error("Failed to create bounty: #{inspect(changeset)}")
+        {:cont, assign(socket, :main_bounty_form, to_form(changeset))}
+    end
+  end
+
   defp handle_event("open_main_bounty_form", _params, socket) do
     {:cont, assign(socket, :main_bounty_form_open?, true)}
   end
@@ -113,6 +167,14 @@ defmodule AlgoraWeb.Org.Nav do
     {:cont, assign(socket, :main_bounty_form_open?, false)}
   end
 
+  defp handle_event("open_main_contract_form", _params, socket) do
+    {:cont, assign(socket, :main_contract_form_open?, true)}
+  end
+
+  defp handle_event("close_main_contract_form", _params, socket) do
+    {:cont, assign(socket, :main_contract_form_open?, false)}
+  end
+
   defp handle_event(_event, _params, socket) do
     {:cont, socket}
   end
diff --git a/lib/algora_web/live/user/profile_live.ex b/lib/algora_web/live/user/profile_live.ex
index dba4b9b42..1380966af 100644
--- a/lib/algora_web/live/user/profile_live.ex
+++ b/lib/algora_web/live/user/profile_live.ex
@@ -138,7 +138,7 @@ defmodule AlgoraWeb.User.ProfileLive do
                                 {ticket.repository.name}#{ticket.number}
                               
                               <.link
-                                :if={!ticket.repository}
+                                :if={!ticket.repository && ticket.url}
                                 href={ticket.url}
                                 class="hover:underline"
                               >
diff --git a/lib/algora_web/router.ex b/lib/algora_web/router.ex
index c22a49ec0..b9f5343ee 100644
--- a/lib/algora_web/router.ex
+++ b/lib/algora_web/router.ex
@@ -200,7 +200,8 @@ defmodule AlgoraWeb.Router do
         live "/bounties/new", Org.BountiesNewLive, :index
         live "/bounties/community", Org.BountiesNewLive, :index
         live "/bounties/:id", BountyLive, :index
-        live "/contracts/:id", Contract.ViewLive
+        # live "/contracts/:id", Contract.ViewLive
+        live "/contracts/:id", ContractLive
         live "/team", Org.TeamLive, :index
         live "/leaderboard", Org.LeaderboardLive, :index
       end
diff --git a/priv/repo/migrations/20250414105225_add_hours_per_week_to_bounty.exs b/priv/repo/migrations/20250414105225_add_hours_per_week_to_bounty.exs
new file mode 100644
index 000000000..8d38eb99b
--- /dev/null
+++ b/priv/repo/migrations/20250414105225_add_hours_per_week_to_bounty.exs
@@ -0,0 +1,9 @@
+defmodule Algora.Repo.Migrations.AddHoursPerWeekToBounty do
+  use Ecto.Migration
+
+  def change do
+    alter table(:bounties) do
+      add :hours_per_week, :integer
+    end
+  end
+end