diff --git a/app/controllers/onboarding_controller.rb b/app/controllers/onboarding_controller.rb index 13b3b9f2..9d5317c9 100644 --- a/app/controllers/onboarding_controller.rb +++ b/app/controllers/onboarding_controller.rb @@ -67,21 +67,25 @@ def update if last_step?(step["key"]) complete_onboarding else - redirect_to onboarding_path + next_key = OnboardingConfig.step_keys[OnboardingConfig.step_keys.index(step["key"]) + 1] + redirect_to onboarding_path(step: next_key) end end private - # Allows navigating back to a previously answered step via ?step= param + # Allows navigating to a previously answered step or the next reachable step via ?step= param def requested_step return unless params[:step] step = OnboardingConfig.find_step(params[:step]) return unless step + step_index = OnboardingConfig.step_keys.index(step["key"]) answered_keys = current_user.onboarding_responses.pluck(:question_key) - step if answered_keys.include?(step["key"]) + + # Allow if this step is answered (revisiting) or the previous step is answered (advancing) + step if answered_keys.include?(step["key"]) || (step_index.zero? || answered_keys.include?(OnboardingConfig.step_keys[step_index - 1])) end def current_step diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 89cd8cd9..bc48faa6 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -75,11 +75,26 @@ def create authorize @project if @project.save - # Redirect to path when created from the onboarding modal so it closes and tooltips update - destination = params[:return_to] == "path" ? path_path : @project - redirect_to destination, notice: "Project created." + if request.headers["X-InertiaUI-Modal"].present? && params[:return_to] != "path" + head :no_content + else + # Onboarding flows use return_to to land on /path autoopen the projects modal + destination = case params[:return_to] + when "path" + path_path + when "path_projects" + path_path(open: "projects", nudge: "read_docs") + else + projects_path + end + redirect_to destination, notice: "Project created." + end else - redirect_back fallback_location: new_project_path, inertia: { errors: @project.errors.messages } + if request.headers["X-InertiaUI-Modal"].present? && params[:return_to] != "path" + render json: { errors: @project.errors.messages }, status: :unprocessable_entity + else + redirect_back fallback_location: new_project_path, inertia: { errors: @project.errors.messages } + end end end @@ -104,16 +119,36 @@ def update authorize @project if @project.update(project_params) - redirect_to @project, notice: "Project updated." + if request.headers["X-InertiaUI-Modal"].present? + head :no_content + else + redirect_to @project, notice: "Project updated." + end else - redirect_back fallback_location: edit_project_path(@project), inertia: { errors: @project.errors.messages } + if request.headers["X-InertiaUI-Modal"].present? + render json: { errors: @project.errors.messages }, status: :unprocessable_entity + else + redirect_back fallback_location: edit_project_path(@project), inertia: { errors: @project.errors.messages } + end end end def destroy authorize @project - @project.discard - redirect_to projects_path, notice: "Project deleted." + + if @project.discard + if request.headers["X-InertiaUI-Modal"].present? + head :no_content + else + redirect_to projects_path, notice: "Project deleted." + end + else + if request.headers["X-InertiaUI-Modal"].present? + render json: { errors: @project.errors.messages }, status: :unprocessable_entity + else + redirect_back fallback_location: project_path(@project), inertia: { errors: @project.errors.messages } + end + end end private diff --git a/app/frontend/components/onboarding/SpeechBubble.tsx b/app/frontend/components/onboarding/SpeechBubble.tsx index f9c7b7a0..f072aaee 100644 --- a/app/frontend/components/onboarding/SpeechBubble.tsx +++ b/app/frontend/components/onboarding/SpeechBubble.tsx @@ -1,27 +1,74 @@ import type { ReactNode } from 'react' +type BubbleDirection = 'left' | 'right' | 'bottom' | 'top' + type BubbleProps = { text?: string children?: ReactNode bg?: string - dir?: string + dir?: BubbleDirection } -const SpeechBubble = ({ text, children, bg = 'white', dir = '' }: BubbleProps) => ( -
{track.title}
diff --git a/app/frontend/components/path/PathNode.tsx b/app/frontend/components/path/PathNode.tsx index f1b12d70..e4982c13 100644 --- a/app/frontend/components/path/PathNode.tsx +++ b/app/frontend/components/path/PathNode.tsx @@ -1,5 +1,5 @@ import { useState, useEffect, useCallback, useContext } from 'react' -import { usePage } from '@inertiajs/react' +import { Link, usePage } from '@inertiajs/react' // @ts-expect-error useModalStack lacks type declarations in this beta package import { ModalLink, useModalStack } from '@inertiaui/modal-react' import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/shared/Tooltip' @@ -82,9 +82,9 @@ export default function PathNode({