diff --git a/.gitignore b/.gitignore index 5ea0b7076..1bc7687ee 100644 --- a/.gitignore +++ b/.gitignore @@ -79,4 +79,4 @@ vendor .venv -staff-search \ No newline at end of file +staff-search.worktrees/ diff --git a/app/assets/stylesheets/pages/_hardware_funding.scss b/app/assets/stylesheets/pages/_hardware_funding.scss index 2bce72668..524bbfb16 100644 --- a/app/assets/stylesheets/pages/_hardware_funding.scss +++ b/app/assets/stylesheets/pages/_hardware_funding.scss @@ -24,10 +24,16 @@ $outpost-teal: #34e0ba; color: #fff; font-weight: 800; - &:hover, - &:focus-visible { + &:hover:not(:disabled), + &:focus-visible:not(:disabled) { filter: brightness(1.05); } + + &:disabled { + background: rgba($outpost-ink, 0.15); + color: rgba($outpost-ink, 0.35); + cursor: not-allowed; + } } .ship-modal__btn--secondary { @@ -37,6 +43,67 @@ $outpost-teal: #34e0ba; } } +// "Do you need funding?" question step +.funding-modal__question-heading { + margin: 0 0 1rem; + font-size: 1.1rem; + font-weight: 800; + text-align: center; + color: $outpost-ink; +} + +.funding-modal__question-btns { + display: flex; + flex-direction: column; + gap: 0.6rem; + margin-bottom: 1rem; +} + +.funding-modal__question-btn { + display: flex; + align-items: center; + justify-content: center; + padding: 0.75rem 1rem; + border-radius: 12px; + font: inherit; + font-weight: 700; + font-size: 1rem; + cursor: pointer; + transition: filter 0.12s ease; + + &:hover { + filter: brightness(1.06); + } + + &--yes { + background: linear-gradient(180deg, $outpost-gold, $outpost-orange); + border: none; + color: #fff; + } + + &--no { + background: rgba($outpost-ink, 0.08); + border: 2px solid rgba($outpost-ink, 0.2); + color: $outpost-ink; + } +} + +.funding-modal__question-cancel { + display: flex; + justify-content: center; +} + +.funding-modal__no-funding-note { + margin: 0 0 1rem; + padding: 0.65rem 0.85rem; + border-radius: 10px; + background: color-mix(in srgb, $outpost-teal 22%, #fff); + border: 1px solid color-mix(in srgb, $outpost-teal 55%, #fff); + font-size: 0.95rem; + color: #0c6b58; + line-height: 1.45; +} + .funding-modal__header { display: flex; flex-direction: column; @@ -196,16 +263,41 @@ $outpost-teal: #34e0ba; .funding-modal__bom-check { display: flex; - align-items: center; - gap: 0.5rem; + align-items: flex-start; + gap: 0.6rem; margin-bottom: 1rem; - font-weight: 600; + padding: 0.65rem 0.75rem; + border: 2px solid rgba($outpost-ink, 0.2); + border-radius: 10px; + background: rgba($outpost-ink, 0.04); font-size: 0.9rem; color: $outpost-ink; cursor: pointer; + transition: + border-color 0.12s ease, + background 0.12s ease; + + &:has(input:checked) { + border-color: $outpost-orange; + background: color-mix(in srgb, $outpost-orange 8%, #fff); + } + + &--required { + border-color: rgba($outpost-orange, 0.6); + background: color-mix(in srgb, $outpost-orange 5%, #fff); + } input[type="checkbox"] { + flex: 0 0 auto; + margin-top: 2px; + width: 1.1rem; + height: 1.1rem; accent-color: $outpost-orange; + cursor: pointer; + } + + strong { + color: $outpost-orange; } code { @@ -216,6 +308,11 @@ $outpost-teal: #34e0ba; } } +// Hide the "up to $X" funding max when user selected the no-funding path +.funding-modal__tiers--hide-max .funding-tier__max { + display: none; +} + // Inline tier-max validation error (shown on cream — red reads fine). .funding-request__error { margin: 0 0 1rem; diff --git a/app/assets/stylesheets/pages/projects/_show.scss b/app/assets/stylesheets/pages/projects/_show.scss index 483159d8e..374207b2f 100644 --- a/app/assets/stylesheets/pages/projects/_show.scss +++ b/app/assets/stylesheets/pages/projects/_show.scss @@ -206,6 +206,18 @@ turbo-frame#project_hero { background: var(--color-brand-orange-soft); color: var(--color-brand-orange); } + + &--design { + background: rgba(#ebb7ff, 0.15); + color: #ebb7ff; + border: 1px solid rgba(#ebb7ff, 0.35); + } + + &--build { + background: rgba(#81ffff, 0.12); + color: #81ffff; + border: 1px solid rgba(#81ffff, 0.3); + } } .project-show__title-input { diff --git a/app/controllers/projects/funding_requests_controller.rb b/app/controllers/projects/funding_requests_controller.rb index b554018f4..122afe6a1 100644 --- a/app/controllers/projects/funding_requests_controller.rb +++ b/app/controllers/projects/funding_requests_controller.rb @@ -16,9 +16,9 @@ def create track_event "funding_requested", { project_id: @project.id, complexity_tier: params[:complexity_tier] } redirect_to project_path(@project), - notice: "Funding request submitted! We'll review your design and get back to you." + notice: "Design submitted for review! We'll get back to you soon." rescue ActiveRecord::RecordNotUnique - redirect_to project_path(@project), alert: "You already have a funding request under review." + redirect_to project_path(@project), alert: "You already have a design review in progress." rescue ActiveRecord::RecordInvalid => e redirect_back fallback_location: project_path(@project), alert: e.record.errors.full_messages.to_sentence diff --git a/app/javascript/controllers/project_builder_controller.js b/app/javascript/controllers/project_builder_controller.js index 68d55c40f..109be4fff 100644 --- a/app/javascript/controllers/project_builder_controller.js +++ b/app/javascript/controllers/project_builder_controller.js @@ -1,10 +1,8 @@ import { Controller } from "@hotwired/stimulus"; // Reveals the Design/Build stage chooser on /projects/new when the user clicks -// "Blank new hardware project +". Software projects submit immediately; hardware -// projects pick a stage first. The collapsed chooser is marked inert so its -// controls drop out of the tab order and the a11y tree; a CSS grid-rows -// transition animates the visual reveal. +// "New hardware project +". The collapsed chooser is marked inert so its +// controls drop out of the tab order; a CSS grid-rows transition animates the reveal. export default class extends Controller { static targets = ["chooser", "hardwareBtn"]; diff --git a/app/models/certification/funding_request.rb b/app/models/certification/funding_request.rb index c8bb34580..217754948 100644 --- a/app/models/certification/funding_request.rb +++ b/app/models/certification/funding_request.rb @@ -92,7 +92,7 @@ def self.tier_discount_summary end validates :complexity_tier, inclusion: { in: TIER_MAX_CENTS.keys } - validates :requested_amount_cents, numericality: { only_integer: true, greater_than: 0 } + validates :requested_amount_cents, numericality: { only_integer: true, greater_than_or_equal_to: 0 } validates :approved_amount_cents, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, allow_nil: true validates :feedback, length: { maximum: 10_000 }, allow_blank: true @@ -296,6 +296,7 @@ def accrue_discount_for_owner! def issue_hcb_grant! return if hcb_grant_hashid.present? + return if final_amount_cents.to_i == 0 owner = project.memberships.owner.first&.user || user grant = HCBService.create_card_grant( @@ -317,9 +318,14 @@ def notify_owner! case status.to_sym when :approved - owner.dm_user("Your hardware project '#{project.title}' was approved for $#{final_amount_dollars} in funding! It's switched to the build phase. Log your build hours with a timelapse and ship when you're ready.") + msg = if final_amount_cents.to_i > 0 + "Your design review for '#{project.title}' was approved — you've been granted $#{final_amount_dollars} to buy your parts! Your project is now in the build phase. Log your build hours and ship when you're ready." + else + "Your design review for '#{project.title}' was approved! Your project is now in the build phase. Log your build hours and ship when you're ready." + end + owner.dm_user(msg) when :returned - msg = "Your funding request for '#{project.title}' needs changes before it can be approved." + msg = "Your design review for '#{project.title}' needs changes before it can be approved." msg += "\n\n#{feedback}" if feedback.present? owner.dm_user(msg) end diff --git a/app/views/projects/new.html.erb b/app/views/projects/new.html.erb index 1b1b6e573..6c150c5bd 100644 --- a/app/views/projects/new.html.erb +++ b/app/views/projects/new.html.erb @@ -109,9 +109,9 @@ <%= hidden_field_tag "project[title]", "Untitled" %> <%= hidden_field_tag "project[hardware_stage]", "design" %> <% end %> @@ -119,9 +119,9 @@ <%= hidden_field_tag "project[title]", "Untitled" %> <%= hidden_field_tag "project[hardware_stage]", "build" %> <% end %> diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb index bd0ca1af1..4337127b3 100644 --- a/app/views/projects/show.html.erb +++ b/app/views/projects/show.html.erb @@ -169,17 +169,17 @@
> - Project stage <%= render "projects/help_tooltip", text: "\"I need Funding\" means you're still designing and need a grant to buy parts. Submit your design for funding to move to building. \"I don't need Funding\" means you already have your parts and are building." %> + Project stage <%= render "projects/help_tooltip", text: "\"Design Stage\" means you're working on your design. Submit your design to move to building. \"Build Stage\" means you have your parts and are actively building." %> <% if @project.has_any_funding_request? %> <% fr = @project.latest_funding_request %>
<% if fr.approved? %> - Design Stage — $<%= fr.final_amount_dollars %> approved + Design Stage — review approved<%= " ($#{fr.final_amount_dollars})" if fr.final_amount_cents.to_i > 0 %> <% elsif fr.pending? %> - Design Stage — $<%= fr.requested_amount_dollars %> under review + Design Stage — review in progress <% else %> - Design Stage — funding returned + Design Stage — review returned <% end %> @@ -192,11 +192,11 @@
<% end %> @@ -386,6 +386,13 @@

<%= @project.title %>

<% if @project.hardware? %> Hardware + <% if Flipper.enabled?(:hardware_flow, current_user) %> + <% if @project.design_stage? %> + Design Stage + <% elsif @project.build_stage? %> + Build Stage + <% end %> + <% end %> <% end %>
@@ -563,7 +570,7 @@ <% if Flipper.enabled?(:hardware_flow, current_user) && is_member && @project.design_stage? %> <% if @project.has_pending_funding_request? %> <%= render ActionButtonComponent.new( - text: "Funding request under review", + text: "Design review in progress", variant: :secondary, type: :button, disabled: :soft, @@ -575,7 +582,7 @@ ) %> <% elsif !@project.info_complete? %> <%= render ActionButtonComponent.new( - text: "Submit Design to Get Project Funding", + text: "Submit Design", variant: :primary, type: :button, icon: "icons/rocket.png", @@ -588,7 +595,7 @@ ) %> <% else %> <%= render ActionButtonComponent.new( - text: "Submit Design to Get Project Funding", + text: "Submit Design", variant: :primary, type: :button, icon: "icons/rocket.png", @@ -1096,21 +1103,52 @@ class="ship-modal ship-modal--post funding-modal--outpost" data-controller="modal" aria-label="Request project funding"> +
+
+ <%= image_tag "outpost.png", alt: "Outpost", class: "funding-modal__logo" %> +

The greatest hardware adventure yet.

+
+ +

Do you need funding to buy parts?

+ +
+ + +
+ +
+ +
+
+ <%= form_with url: project_funding_request_path(@project), method: :post, data: { turbo: false }, - html: { class: "ship-modal__form", data: { tier_maxes: Certification::FundingRequest::TIER_MAX_DOLLARS.to_json, tier_discounts: Certification::FundingRequest.tier_discount_summary.to_json } } do |form| %> + html: { id: "funding-modal-form-#{@project.id}", class: "ship-modal__form", style: "display:none", data: { tier_maxes: Certification::FundingRequest::TIER_MAX_DOLLARS.to_json, tier_discounts: Certification::FundingRequest.tier_discount_summary.to_json } } do |form| %> + <%= form.hidden_field :requested_amount, value: "", id: "funding-modal-amount-#{@project.id}" %>
<%= image_tag "outpost.png", alt: "Outpost", class: "funding-modal__logo" %> -

The greatest hardware adventure yet. Get your build funded.

+

-
+
Pick your complexity tier <% Certification::FundingRequest::TIERS.each do |tier_id, t| %> <% end %>
- - - -

- Get your design approved to earn Stardust toward the Outpost Ticket — higher tiers cover more of it. - <%= link_to "How tiers work", guide_path("tiers"), class: "funding-modal__guide-link", target: "_blank", rel: "noopener" %> -

+
+ + + +

+ Get your design approved to earn Stardust toward the Outpost Ticket — higher tiers cover more of it. + <%= link_to "How tiers work", guide_path("tiers"), class: "funding-modal__guide-link", target: "_blank", rel: "noopener" %> +

+ + +
- +
- <%= form.submit "Submit for funding", class: "ship-modal__btn ship-modal__btn--primary funding-modal__submit", data: { disable_with: "Submitting…" } %> + <%= form.submit "Submit design", disabled: true, class: "ship-modal__btn ship-modal__btn--primary funding-modal__submit", data: { disable_with: "Submitting…" } %>
<% end %>