diff --git a/.gitignore b/.gitignore index a06a94d..15341d4 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,6 @@ # Ignore key files for decrypting credentials and more. /config/credentials/*.key +# Ignore personal local Claude instructions. +/CLAUDE.local.md + diff --git a/app/models/organization.rb b/app/models/organization.rb index 3345930..f68f3b6 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -13,6 +13,6 @@ class Organization < ApplicationRecord length: { maximum: 63 }, exclusion: { in: %w[ www mail ftp admin api app root ], message: "is reserved" } validates :website, - format: { with: URI::DEFAULT_PARSER.make_regexp(%w[ http https ]), message: "must be a valid URL" }, - allow_blank: true + presence: true, + format: { with: URI::DEFAULT_PARSER.make_regexp(%w[ http https ]), message: "must be a valid URL" } end diff --git a/app/views/home/sections/_contact_button.html.erb b/app/views/home/sections/_contact_button.html.erb new file mode 100644 index 0000000..9375308 --- /dev/null +++ b/app/views/home/sections/_contact_button.html.erb @@ -0,0 +1,2 @@ +<%= link_to "Contact us", Current.organization.website, target: "_blank", rel: "noopener", + class: "inline-block rounded-md border border-line bg-surface px-6 py-2.5 font-medium text-ink hover:bg-canvas transition" %> diff --git a/app/views/home/sections/_faq.html.erb b/app/views/home/sections/_faq.html.erb new file mode 100644 index 0000000..d50675a --- /dev/null +++ b/app/views/home/sections/_faq.html.erb @@ -0,0 +1,37 @@ +<% faqs = [ + { q: "What is legacy giving?", + a: "Legacy giving is a way to support the causes and communities you care about through a planned gift, often as part of your estate or long-term financial plans." }, + { q: "Do I need to have a plan before I start?", + a: "Not at all. You can explore ideas and learn about your options without any commitment. There's no requirement to decide anything right away." }, + { q: "Is there any cost or obligation to explore?", + a: "No. Exploring possibilities and starting a conversation is free and carries no obligation to follow through." }, + { q: "Can I change my mind later?", + a: "Yes. Your priorities can evolve over time, and you can revisit and update your reflections whenever you'd like." }, + { q: "How do I get help from the team?", + a: "You can reach out anytime to ask questions or discuss ideas. The team is here to help you navigate the possibilities at your own pace." } +] %> + +
+
+

Frequently asked questions

+ +
+ <% faqs.each do |faq| %> +
+ + <%= faq[:q] %> + + + +

<%= faq[:a] %>

+
+ <% end %> +
+ +
+

Still have questions?

+
+ <%= render "home/sections/contact_button" %> +
+
+
+
diff --git a/app/views/home/sections/_hero.html.erb b/app/views/home/sections/_hero.html.erb new file mode 100644 index 0000000..e5b21a5 --- /dev/null +++ b/app/views/home/sections/_hero.html.erb @@ -0,0 +1,22 @@ +
+
+

Your Legacy Starts Here

+

You don't need to have everything figured out to begin

+

+ Explore ideas, discover possibilities, and connect with + <%= Current.organization.name %> whenever you're ready. +

+ +
+ <% if authenticated? %> + <%= link_to "Explore options", scenarios_path, + class: "rounded-md bg-accent px-6 py-2.5 font-medium text-white hover:bg-[#444] transition" %> + <% else %> + <%= link_to "Sign up", new_registration_path, + class: "rounded-md bg-accent px-6 py-2.5 font-medium text-white hover:bg-[#444] transition" %> + <%= link_to "Log in", new_session_path, + class: "rounded-md border border-line bg-surface px-6 py-2.5 font-medium text-ink hover:bg-canvas transition" %> + <% end %> +
+
+
diff --git a/app/views/home/sections/_pillars.html.erb b/app/views/home/sections/_pillars.html.erb new file mode 100644 index 0000000..151f589 --- /dev/null +++ b/app/views/home/sections/_pillars.html.erb @@ -0,0 +1,34 @@ +<% pillars = [ + { title: "Explore possibilities", body: "Learn about different approaches to legacy giving and the impact they can create." }, + { title: "Reflect on What Matters", body: "Capture ideas, values, and priorities as they evolve over time." }, + { title: "Learn Without Commitment", body: "Compare options and explore possibilities before making decisions." }, + { title: "Connect With the Advisor", body: "Ask questions, discuss ideas, and get support whenever you need it." } +] %> + +
+
+
+

Explore, Reflect, and Plan at Your Own Pace

+

You don't need to have everything figured out to begin.

+

+ Explore ideas, learn about different options, and capture what matters most to you. When you're + ready or whenever questions come up, the <%= Current.organization.name %> team is here to help + you navigate the possibilities. +

+
+ +
+ <% pillars.each do |pillar| %> +
+

<%= pillar[:title] %>

+

<%= pillar[:body] %>

+
+ <% end %> +
+ +
+ <%= link_to "Explore the workspace", get_started_path, + class: "inline-block rounded-md border border-line bg-surface px-6 py-2.5 font-medium text-ink hover:bg-canvas transition" %> +
+
+
diff --git a/app/views/home/sections/_questions.html.erb b/app/views/home/sections/_questions.html.erb new file mode 100644 index 0000000..4be6122 --- /dev/null +++ b/app/views/home/sections/_questions.html.erb @@ -0,0 +1,19 @@ +
+
+

Have Questions?

+ +

+ You don't need to have everything figured out before reaching out. There is no obligation + or commitment to start a conversation. +

+

+ Whether you're simply curious about legacy giving, exploring possibilities, or considering + specific options, the <%= Current.organization.name %> team is happy to answer questions and + help you learn more. +

+ +
+ <%= render "home/sections/contact_button" %> +
+
+
diff --git a/app/views/home/sections/_scenario_example.html.erb b/app/views/home/sections/_scenario_example.html.erb new file mode 100644 index 0000000..19c3a1e --- /dev/null +++ b/app/views/home/sections/_scenario_example.html.erb @@ -0,0 +1,53 @@ +<% segments = [ + { label: "Greatest Community Need", pct: 30, color: "#E07B39" }, + { label: "Program: Education", pct: 30, color: "#7C3AED" }, + { label: "Population: Youth", pct: 25, color: "#4FA89B" }, + { label: "Specific org", pct: 15, color: "#9B5DE5" } +] %> + +
+
+
+

What Could Your Legacy Support?

+

+ This is only an example. Your legacy plan can reflect the causes, communities, + and organizations that matter most to you. +

+
+ +
+ <%# TODO: placeholder quote — replace with real donor quote before launch. Remove the red dashed wrapper too. %> +
+

Placeholder — replace this quote

+
+ “I wanted part of my gift to go where it's needed most, and part to something close to my heart.” +
+
— Sarah, donor
+
+ +
+ <% segments.each do |segment| %> +
+ <% end %> +
+ +
    + <% segments.each do |segment| %> +
  • + + + <%= segment[:label] %> + + <%= segment[:pct] %>% +
  • + <% end %> +
+
+ +
+

Curious what your own priorities might look like?

+ <%= link_to "Explore your own scenario", get_started_path, + class: "mt-6 inline-block rounded-md border border-line bg-surface px-6 py-2.5 font-medium text-ink hover:bg-canvas transition" %> +
+
+
diff --git a/app/views/home/sections/_stories.html.erb b/app/views/home/sections/_stories.html.erb new file mode 100644 index 0000000..b929e2f --- /dev/null +++ b/app/views/home/sections/_stories.html.erb @@ -0,0 +1,17 @@ +
+
+

Legacy stories

+ +
+ <% 3.times do %> +
+ <% end %> +
+ +
+

Inspired by these stories?

+ <%= link_to "Start your reflection", get_started_path, + class: "mt-6 inline-block rounded-md border border-line bg-surface px-6 py-2.5 font-medium text-ink hover:bg-canvas transition" %> +
+
+
diff --git a/app/views/home/show.html.erb b/app/views/home/show.html.erb index e80d039..cee139a 100644 --- a/app/views/home/show.html.erb +++ b/app/views/home/show.html.erb @@ -1,21 +1,8 @@ -
- <% if authenticated? %> -

- Hello, <%= Current.user.email_address %> -

-

You're signed in to <%= Current.organization.name %>.

+<% get_started_path = authenticated? ? scenarios_path : new_registration_path %> -
- <%= link_to "Explore options", scenarios_path, class: "rounded-md px-3.5 py-2.5 bg-accent hover:bg-[#444] text-white font-medium inline-block transition" %> - <%= button_to "Sign out", session_path, method: :delete, class: "rounded-md px-3.5 py-2.5 border border-line bg-surface hover:bg-canvas text-ink-soft font-medium cursor-pointer transition" %> -
- <% else %> -

Welcome to <%= Current.organization.name %>

-

Sign in to your account or create a new one to get started.

- -
- <%= link_to "Sign in", new_session_path, class: "rounded-md px-3.5 py-2.5 bg-accent hover:bg-[#444] text-white font-medium inline-block transition" %> - <%= link_to "Sign up", new_registration_path, class: "rounded-md px-3.5 py-2.5 border border-line bg-surface hover:bg-canvas text-ink-soft font-medium inline-block transition" %> -
- <% end %> -
+<%= render "home/sections/hero", get_started_path: get_started_path %> +<%= render "home/sections/pillars", get_started_path: get_started_path %> +<%= render "home/sections/scenario_example", get_started_path: get_started_path %> +<%= render "home/sections/stories", get_started_path: get_started_path %> +<%= render "home/sections/questions" %> +<%= render "home/sections/faq" %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 344bafb..55a2308 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -32,20 +32,22 @@ <%= link_to (Current.organization&.name || "Community Foundations"), root_path, class: "font-serif text-lg tracking-tight text-ink hover:text-ink" %> - <% if Rails.env.development? || authenticated? %> -
- <% if Rails.env.development? %> - <%= link_to "Mailer previews", "/rails/mailers", class: "text-ink-soft hover:text-ink" %> +
+ <% if Rails.env.development? %> + <%= link_to "Mailer previews", "/rails/mailers", class: "text-ink-soft hover:text-ink" %> + <% end %> + <% if authenticated? %> + <% if Current.user&.admin_of?(Current.organization) %> + <%= link_to "Members", organization_memberships_path, class: "text-ink-soft hover:text-ink" %> <% end %> - <% if authenticated? %> - <% if Current.user&.admin_of?(Current.organization) %> - <%= link_to "Members", organization_memberships_path, class: "text-ink-soft hover:text-ink" %> - <% end %> - <%= link_to "Settings", users_password_path, class: "text-ink-soft hover:text-ink" %> - <%= button_to "Sign out", session_path, method: :delete, class: "text-ink-soft hover:text-ink cursor-pointer" %> - <% end %> -
- <% end %> + <%= link_to "Settings", users_password_path, class: "text-ink-soft hover:text-ink" %> + <%= button_to "Sign out", session_path, method: :delete, class: "text-ink-soft hover:text-ink cursor-pointer" %> + <% elsif Current.organization %> + <%= link_to "Log in", new_session_path, class: "text-ink-soft hover:text-ink" %> + <%= link_to "Explore the legacy builder platform", new_registration_path, + class: "rounded-md bg-accent px-3.5 py-2 font-medium text-white hover:bg-[#444] transition" %> + <% end %> +
<%= render "shared/flash" %> diff --git a/test/controllers/home_controller_test.rb b/test/controllers/home_controller_test.rb index 377ce47..ada8624 100644 --- a/test/controllers/home_controller_test.rb +++ b/test/controllers/home_controller_test.rb @@ -1,24 +1,24 @@ require "test_helper" class HomeControllerTest < ActionDispatch::IntegrationTest - test "show on a tenant subdomain offers sign in and sign up when signed out" do + test "show on a tenant subdomain offers sign up and log in when signed out" do host! "arlington.localhost" get root_url assert_response :success - assert_select "h1", /Welcome to #{Regexp.escape(organizations(:arlington).name)}/ - assert_select "a[href=?]", new_session_path, text: "Sign in" + assert_select "h1", "Your Legacy Starts Here" assert_select "a[href=?]", new_registration_path, text: "Sign up" + assert_select "a[href=?]", new_session_path, text: "Log in" end - test "show greets a member by email and offers sign out when signed in" do + test "show offers explore options and sign out when signed in" do host! "arlington.localhost" sign_in_as(users(:one)) get root_url assert_response :success - assert_select "h1", /Hello, #{Regexp.escape(users(:one).email_address)}/ + assert_select "a[href=?]", scenarios_path, text: "Explore options" assert_select "button", text: "Sign out" end diff --git a/test/models/organization_test.rb b/test/models/organization_test.rb index fb40457..6e978e8 100644 --- a/test/models/organization_test.rb +++ b/test/models/organization_test.rb @@ -42,8 +42,13 @@ class OrganizationTest < ActiveSupport::TestCase assert_includes org.errors[:subdomain], "is reserved" end - test "rejects an invalid website but allows a blank one" do - assert Organization.new(name: "Test", subdomain: "test").valid? + test "requires a website" do + org = Organization.new(name: "Test", subdomain: "test") + assert_not org.valid? + assert_includes org.errors[:website], "can't be blank" + end + + test "rejects an invalid website" do invalid = Organization.new(name: "Test", subdomain: "test2", website: "not a url") assert_not invalid.valid? assert_includes invalid.errors[:website], "must be a valid URL"