saas_platform 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +143 -0
  4. data/Rakefile +14 -0
  5. data/app/assets/stylesheets/saas_platform/application.css +15 -0
  6. data/app/assets/stylesheets/saas_platform/application.tailwind.css +25 -0
  7. data/app/components/saas_platform/button_component.rb +46 -0
  8. data/app/components/saas_platform/card_component.html.erb +3 -0
  9. data/app/components/saas_platform/card_component.rb +29 -0
  10. data/app/components/saas_platform/notification_component.html.erb +27 -0
  11. data/app/components/saas_platform/notification_component.rb +11 -0
  12. data/app/components/saas_platform/order_list_component.html.erb +28 -0
  13. data/app/components/saas_platform/order_list_component.rb +18 -0
  14. data/app/controllers/saas_platform/application_controller.rb +31 -0
  15. data/app/controllers/saas_platform/audit_logs_controller.rb +22 -0
  16. data/app/controllers/saas_platform/dashboard_controller.rb +14 -0
  17. data/app/controllers/saas_platform/devise/confirmations_controller.rb +7 -0
  18. data/app/controllers/saas_platform/devise/passwords_controller.rb +7 -0
  19. data/app/controllers/saas_platform/devise/registrations_controller.rb +7 -0
  20. data/app/controllers/saas_platform/devise/sessions_controller.rb +7 -0
  21. data/app/controllers/saas_platform/devise/unlocks_controller.rb +7 -0
  22. data/app/controllers/saas_platform/search_controller.rb +15 -0
  23. data/app/helpers/saas_platform/application_helper.rb +4 -0
  24. data/app/jobs/saas_platform/application_job.rb +4 -0
  25. data/app/mailers/saas_platform/application_mailer.rb +6 -0
  26. data/app/models/saas_platform/account.rb +36 -0
  27. data/app/models/saas_platform/api_key.rb +24 -0
  28. data/app/models/saas_platform/application_record.rb +5 -0
  29. data/app/models/saas_platform/notification.rb +19 -0
  30. data/app/models/saas_platform/order.rb +43 -0
  31. data/app/models/saas_platform/order_item.rb +18 -0
  32. data/app/models/saas_platform/product.rb +15 -0
  33. data/app/models/saas_platform/role.rb +15 -0
  34. data/app/models/saas_platform/transaction.rb +15 -0
  35. data/app/models/saas_platform/user.rb +52 -0
  36. data/app/models/saas_platform/wallet.rb +38 -0
  37. data/app/models/saas_platform/webhook_endpoint.rb +18 -0
  38. data/app/models/saas_platform/webhook_event.rb +10 -0
  39. data/app/policies/saas_platform/application_policy.rb +53 -0
  40. data/app/services/saas_platform/analytics_service.rb +29 -0
  41. data/app/services/saas_platform/payment_service.rb +32 -0
  42. data/app/services/saas_platform/plan_service.rb +45 -0
  43. data/app/services/saas_platform/search_service.rb +20 -0
  44. data/app/views/devise/registrations/new.html.erb +59 -0
  45. data/app/views/devise/saas_platform_users/confirmations/new.html.erb +16 -0
  46. data/app/views/devise/saas_platform_users/mailer/confirmation_instructions.html.erb +5 -0
  47. data/app/views/devise/saas_platform_users/mailer/email_changed.html.erb +7 -0
  48. data/app/views/devise/saas_platform_users/mailer/password_change.html.erb +3 -0
  49. data/app/views/devise/saas_platform_users/mailer/reset_password_instructions.html.erb +8 -0
  50. data/app/views/devise/saas_platform_users/mailer/unlock_instructions.html.erb +7 -0
  51. data/app/views/devise/saas_platform_users/passwords/edit.html.erb +25 -0
  52. data/app/views/devise/saas_platform_users/passwords/new.html.erb +16 -0
  53. data/app/views/devise/saas_platform_users/registrations/edit.html.erb +43 -0
  54. data/app/views/devise/saas_platform_users/registrations/new.html.erb +29 -0
  55. data/app/views/devise/saas_platform_users/sessions/new.html.erb +26 -0
  56. data/app/views/devise/saas_platform_users/shared/_error_messages.html.erb +15 -0
  57. data/app/views/devise/saas_platform_users/shared/_links.html.erb +25 -0
  58. data/app/views/devise/saas_platform_users/unlocks/new.html.erb +16 -0
  59. data/app/views/devise/sessions/new.html.erb +47 -0
  60. data/app/views/layouts/saas_platform/application.html.erb +17 -0
  61. data/app/views/layouts/saas_platform/auth.html.erb +48 -0
  62. data/app/views/layouts/saas_platform/dashboard.html.erb +76 -0
  63. data/app/views/saas_platform/audit_logs/index.html.erb +43 -0
  64. data/app/views/saas_platform/dashboard/index.html.erb +50 -0
  65. data/app/views/saas_platform/search/index.html.erb +75 -0
  66. data/config/initializers/devise.rb +313 -0
  67. data/config/initializers/money.rb +115 -0
  68. data/config/initializers/rack_attack.rb +27 -0
  69. data/config/locales/devise.en.yml +65 -0
  70. data/config/routes.rb +16 -0
  71. data/config/tailwind.config.js +45 -0
  72. data/db/migrate/20260603000000_devise_create_saas_platform_users.rb +53 -0
  73. data/db/migrate/20260603000001_rolify_create_saas_platform_roles.rb +19 -0
  74. data/db/migrate/20260603000002_create_saas_platform_accounts.rb +20 -0
  75. data/db/migrate/20260603000003_create_saas_platform_banking.rb +28 -0
  76. data/db/migrate/20260603000004_create_saas_platform_ecommerce.rb +37 -0
  77. data/db/migrate/20260603000005_create_versions.rb +14 -0
  78. data/db/migrate/20260603000006_create_saas_platform_api_keys.rb +17 -0
  79. data/db/migrate/20260603000007_create_saas_platform_webhooks.rb +25 -0
  80. data/db/migrate/20260603000008_create_saas_platform_notifications.rb +14 -0
  81. data/db/migrate/20260603000009_add_subscription_to_saas_platform_accounts.rb +12 -0
  82. data/lib/saas_platform/engine.rb +5 -0
  83. data/lib/saas_platform/version.rb +3 -0
  84. data/lib/saas_platform.rb +27 -0
  85. data/lib/tasks/saas_platform_tasks.rake +4 -0
  86. metadata +562 -0
@@ -0,0 +1,26 @@
1
+ <h2>Log in</h2>
2
+
3
+ <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
4
+ <div class="field">
5
+ <%= f.label :email %><br />
6
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
7
+ </div>
8
+
9
+ <div class="field">
10
+ <%= f.label :password %><br />
11
+ <%= f.password_field :password, autocomplete: "current-password" %>
12
+ </div>
13
+
14
+ <% if devise_mapping.rememberable? %>
15
+ <div class="field">
16
+ <%= f.check_box :remember_me %>
17
+ <%= f.label :remember_me %>
18
+ </div>
19
+ <% end %>
20
+
21
+ <div class="actions">
22
+ <%= f.submit "Log in" %>
23
+ </div>
24
+ <% end %>
25
+
26
+ <%= render "saas_platform_users/shared/links" %>
@@ -0,0 +1,15 @@
1
+ <% if resource.errors.any? %>
2
+ <div id="error_explanation" data-turbo-cache="false">
3
+ <h2>
4
+ <%= I18n.t("errors.messages.not_saved",
5
+ count: resource.errors.count,
6
+ resource: resource.class.model_name.human.downcase)
7
+ %>
8
+ </h2>
9
+ <ul>
10
+ <% resource.errors.full_messages.each do |message| %>
11
+ <li><%= message %></li>
12
+ <% end %>
13
+ </ul>
14
+ </div>
15
+ <% end %>
@@ -0,0 +1,25 @@
1
+ <%- if controller_name != 'sessions' %>
2
+ <%= link_to "Log in", new_session_path(resource_name) %><br />
3
+ <% end %>
4
+
5
+ <%- if devise_mapping.registerable? && controller_name != 'registrations' %>
6
+ <%= link_to "Sign up", new_registration_path(resource_name) %><br />
7
+ <% end %>
8
+
9
+ <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
10
+ <%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
11
+ <% end %>
12
+
13
+ <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
14
+ <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
15
+ <% end %>
16
+
17
+ <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
18
+ <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
19
+ <% end %>
20
+
21
+ <%- if devise_mapping.omniauthable? %>
22
+ <%- resource_class.omniauth_providers.each do |provider| %>
23
+ <%= button_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false } %><br />
24
+ <% end %>
25
+ <% end %>
@@ -0,0 +1,16 @@
1
+ <h2>Resend unlock instructions</h2>
2
+
3
+ <%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
4
+ <%= render "saas_platform_users/shared/error_messages", resource: resource %>
5
+
6
+ <div class="field">
7
+ <%= f.label :email %><br />
8
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
9
+ </div>
10
+
11
+ <div class="actions">
12
+ <%= f.submit "Resend unlock instructions" %>
13
+ </div>
14
+ <% end %>
15
+
16
+ <%= render "saas_platform_users/shared/links" %>
@@ -0,0 +1,47 @@
1
+ <% content_for :header do %>
2
+ Sign in to your account
3
+ <% end %>
4
+
5
+ <%= form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: "space-y-6" }) do |f| %>
6
+ <div>
7
+ <%= f.label :email, class: "block text-sm font-medium text-apple-gray" %>
8
+ <div class="mt-1">
9
+ <%= f.email_field :email, autofocus: true, autocomplete: "email", class: "block w-full appearance-none rounded-apple border border-gray-200 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-apple-blue focus:outline-none focus:ring-apple-blue sm:text-sm transition-all" %>
10
+ </div>
11
+ </div>
12
+
13
+ <div>
14
+ <%= f.label :password, class: "block text-sm font-medium text-apple-gray" %>
15
+ <div class="mt-1">
16
+ <%= f.password_field :password, autocomplete: "current-password", class: "block w-full appearance-none rounded-apple border border-gray-200 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-apple-blue focus:outline-none focus:ring-apple-blue sm:text-sm transition-all" %>
17
+ </div>
18
+ </div>
19
+
20
+ <% if devise_mapping.rememberable? %>
21
+ <div class="flex items-center justify-between">
22
+ <div class="flex items-center">
23
+ <%= f.check_box :remember_me, class: "h-4 w-4 rounded border-gray-300 text-apple-blue focus:ring-apple-blue" %>
24
+ <%= f.label :remember_me, class: "ml-2 block text-sm text-apple-gray" %>
25
+ </div>
26
+
27
+ <div class="text-sm">
28
+ <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
29
+ <%= link_to "Forgot your password?", new_password_path(resource_name), class: "font-medium text-apple-blue hover:text-blue-500" %>
30
+ <% end %>
31
+ </div>
32
+ </div>
33
+ <% end %>
34
+
35
+ <div>
36
+ <%= render SaasPlatform::ButtonComponent.new(variant: :primary, type: :submit, class: "w-full") do %>
37
+ Sign In
38
+ <% end %>
39
+ </div>
40
+ <% end %>
41
+
42
+ <% content_for :footer do %>
43
+ <p class="text-sm text-apple-gray">
44
+ Don't have an account?
45
+ <%= link_to "Sign up now", new_registration_path(resource_name), class: "font-medium text-apple-blue hover:text-blue-500" %>
46
+ </p>
47
+ <% end %>
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Saas platform</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= yield :head %>
9
+
10
+ <%= stylesheet_link_tag "saas_platform/application", media: "all" %>
11
+ </head>
12
+ <body>
13
+
14
+ <%= yield %>
15
+
16
+ </body>
17
+ </html>
@@ -0,0 +1,48 @@
1
+ <!DOCTYPE html>
2
+ <html class="h-full bg-apple-light">
3
+ <head>
4
+ <title>SaasPlatform Auth</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <%= csrf_meta_tags %>
7
+ <%= csp_meta_tag %>
8
+
9
+ <%= stylesheet_link_tag "saas_platform/application", "data-turbo-track": "reload" %>
10
+ <%= javascript_importmap_tags "saas_platform" %>
11
+ </head>
12
+
13
+ <body class="h-full font-sans antialiased text-apple-dark">
14
+ <div class="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8 bg-gradient-to-br from-blue-50 to-white">
15
+ <div class="sm:mx-auto sm:w-full sm:max-w-md">
16
+ <div class="flex justify-center mb-6">
17
+ <div class="w-12 h-12 rounded-apple bg-apple-blue flex items-center justify-center text-white shadow-apple">
18
+ <svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 11c0 3.517-1.009 6.799-2.753 9.571m-3.44-2.04l.054-.09A10.003 10.003 0 0012 3c1.268 0 2.39.234 3.417.659m-5.74 2.13a1.949 1.949 0 01-1.951 1.57H4a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-6a2 2 0 012-2h2.25c.594 0 1.14-.135 1.634-.38m-9.97-4.21c.066.458.11 1.012.11 1.591v3.022m0 0l-1.07 1.07m1.07-1.07l1.07 1.07M19 12h.01M19 15h.01M19 18h.01"></path></svg>
19
+ </div>
20
+ </div>
21
+ <h2 class="text-center text-3xl font-bold tracking-tight text-apple-dark">
22
+ <%= yield(:header) %>
23
+ </h2>
24
+ </div>
25
+
26
+ <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
27
+ <%= render SaasPlatform::CardComponent.new(glass: true, padding: :lg, class: "mx-4 sm:mx-0") do %>
28
+ <% if notice %>
29
+ <div class="mb-4 p-3 rounded-apple bg-apple-green/10 text-apple-green text-sm font-medium">
30
+ <%= notice %>
31
+ </div>
32
+ <% end %>
33
+ <% if alert %>
34
+ <div class="mb-4 p-3 rounded-apple bg-apple-red/10 text-apple-red text-sm font-medium">
35
+ <%= alert %>
36
+ </div>
37
+ <% end %>
38
+
39
+ <%= yield %>
40
+ <% end %>
41
+
42
+ <div class="mt-6 text-center">
43
+ <%= yield(:footer) %>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </body>
48
+ </html>
@@ -0,0 +1,76 @@
1
+ <!DOCTYPE html>
2
+ <html class="h-full bg-apple-light">
3
+ <head>
4
+ <title><%= content_for?(:title) ? yield(:title) : "SaasPlatform Dashboard" %></title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <%= csrf_meta_tags %>
7
+ <%= csp_meta_tag %>
8
+
9
+ <%= stylesheet_link_tag "saas_platform/application", "data-turbo-track": "reload" %>
10
+ <%= javascript_importmap_tags "saas_platform" %>
11
+ </head>
12
+
13
+ <body class="h-full font-sans antialiased text-apple-dark">
14
+ <div class="flex h-screen overflow-hidden">
15
+ <!-- Sidebar -->
16
+ <aside class="hidden md:flex md:flex-shrink-0">
17
+ <div class="flex flex-col w-64 apple-glass border-r border-white/20">
18
+ <div class="flex flex-col flex-grow pt-5 pb-4 overflow-y-auto">
19
+ <div class="flex items-center flex-shrink-0 px-4 mb-8">
20
+ <span class="text-xl font-bold tracking-tight text-apple-blue">SaasPlatform</span>
21
+ </div>
22
+ <nav class="flex-1 px-2 space-y-1">
23
+ <a href="#" class="flex items-center px-3 py-2 text-sm font-medium rounded-apple text-apple-blue bg-blue-50/50">
24
+ <svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path></svg>
25
+ Dashboard
26
+ </a>
27
+ <!-- More nav items -->
28
+ </nav>
29
+ </div>
30
+ </div>
31
+ </aside>
32
+
33
+ <div class="flex flex-col flex-1 w-0 overflow-hidden">
34
+ <!-- Top Nav -->
35
+ <header class="relative z-10 flex flex-shrink-0 h-16 apple-glass">
36
+ <div class="flex justify-between flex-1 px-4">
37
+ <div class="flex flex-1 items-center">
38
+ <!-- Global Search -->
39
+ <form action="<%= saas_platform.search_path %>" method="get" class="w-full max-w-lg lg:max-w-xs">
40
+ <label for="search" class="sr-only">Search</label>
41
+ <div class="relative text-apple-gray focus-within:text-apple-blue">
42
+ <div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
43
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
44
+ </div>
45
+ <input id="search" name="q" class="block w-full py-2 pl-10 pr-3 text-sm placeholder-apple-gray bg-white/50 border border-transparent rounded-apple leading-5 focus:outline-none focus:bg-white focus:ring-1 focus:ring-apple-blue focus:border-apple-blue sm:text-sm transition-all" placeholder="Search everything..." type="search">
46
+ </div>
47
+ </form>
48
+ </div>
49
+ <div class="flex items-center ml-4 md:ml-6 space-x-4">
50
+ <%= render SaasPlatform::NotificationComponent.new(notifications: current_account.notifications.unread) if current_account %>
51
+
52
+ <!-- User Menu -->
53
+ <div class="relative ml-3">
54
+ <div class="flex items-center">
55
+ <span class="mr-3 text-sm font-medium text-apple-gray"><%= current_user&.name %></span>
56
+ <div class="w-8 h-8 rounded-full bg-apple-blue/20 flex items-center justify-center text-apple-blue font-bold">
57
+ <%= current_user&.first_name&.first %>
58
+ </div>
59
+ </div>
60
+ </div>
61
+ </div>
62
+ </div>
63
+ </header>
64
+
65
+ <!-- Main Content -->
66
+ <main class="relative flex-1 overflow-y-auto focus:outline-none bg-apple-light/50">
67
+ <div class="py-6">
68
+ <div class="px-4 mx-auto max-w-7xl sm:px-6 md:px-8">
69
+ <%= yield %>
70
+ </div>
71
+ </div>
72
+ </main>
73
+ </div>
74
+ </div>
75
+ </body>
76
+ </html>
@@ -0,0 +1,43 @@
1
+ <div class="space-y-6">
2
+ <div class="flex items-center justify-between">
3
+ <h1 class="text-2xl font-bold text-apple-dark">Audit Logs</h1>
4
+ <span class="px-3 py-1 rounded-full text-xs font-semibold bg-apple-blue/10 text-apple-blue">Enterprise Feature</span>
5
+ </div>
6
+
7
+ <%= render SaasPlatform::CardComponent.new(padding: :none) do %>
8
+ <div class="overflow-x-auto">
9
+ <table class="min-w-full divide-y divide-gray-200">
10
+ <thead class="bg-apple-light/30">
11
+ <tr>
12
+ <th class="px-6 py-3 text-left text-xs font-medium text-apple-gray uppercase tracking-wider">Event</th>
13
+ <th class="px-6 py-3 text-left text-xs font-medium text-apple-gray uppercase tracking-wider">Item Type</th>
14
+ <th class="px-6 py-3 text-left text-xs font-medium text-apple-gray uppercase tracking-wider">User</th>
15
+ <th class="px-6 py-3 text-left text-xs font-medium text-apple-gray uppercase tracking-wider">Changes</th>
16
+ <th class="px-6 py-3 text-left text-xs font-medium text-apple-gray uppercase tracking-wider">Timestamp</th>
17
+ </tr>
18
+ </thead>
19
+ <tbody class="divide-y divide-gray-200">
20
+ <% @versions.each do |version| %>
21
+ <tr class="hover:bg-apple-light/20 transition-colors">
22
+ <td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
23
+ <span class="px-2 py-1 rounded-md text-[10px] font-bold uppercase <%= version.event == 'create' ? 'bg-apple-green text-white' : (version.event == 'destroy' ? 'bg-apple-red text-white' : 'bg-apple-blue text-white') %>">
24
+ <%= version.event %>
25
+ </span>
26
+ </td>
27
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-apple-dark"><%= version.item_type.split('::').last %></td>
28
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-apple-gray"><%= version.whodunnit || 'System' %></td>
29
+ <td class="px-6 py-4 text-sm text-apple-gray">
30
+ <% if version.object_changes %>
31
+ <code class="text-[10px] bg-gray-50 p-1 rounded truncate block max-w-xs"><%= version.object_changes.truncate(50) %></code>
32
+ <% else %>
33
+ -
34
+ <% end %>
35
+ </td>
36
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-apple-gray"><%= version.created_at.strftime("%b %d, %H:%M:%S") %></td>
37
+ </tr>
38
+ <% end %>
39
+ </tbody>
40
+ </table>
41
+ </div>
42
+ <% end %>
43
+ </div>
@@ -0,0 +1,50 @@
1
+ <div class="space-y-6">
2
+ <div class="flex items-center justify-between">
3
+ <h1 class="text-2xl font-bold text-apple-dark">Welcome back, <%= current_user.first_name %></h1>
4
+ <%= render SaasPlatform::ButtonComponent.new(variant: :primary) do %>
5
+ Create New Project
6
+ <% end %>
7
+ </div>
8
+
9
+ <div class="grid grid-cols-1 gap-6 lg:grid-cols-2 mt-6">
10
+ <%= render SaasPlatform::CardComponent.new do %>
11
+ <h3 class="text-lg font-semibold mb-4">Revenue Growth</h3>
12
+ <%= line_chart @revenue_data, colors: ["#007AFF"], library: { tension: 0.4 } %>
13
+ <% end %>
14
+
15
+ <%= render SaasPlatform::CardComponent.new do %>
16
+ <h3 class="text-lg font-semibold mb-4">Top Selling Products</h3>
17
+ <%= column_chart @top_products, colors: ["#34C759"] %>
18
+ <% end %>
19
+ </div>
20
+
21
+ <div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
22
+ <%= render SaasPlatform::CardComponent.new do %>
23
+ <h3 class="text-lg font-semibold mb-2">Total Revenue</h3>
24
+ <p class="text-3xl font-bold text-apple-blue">$<%= number_with_precision(@revenue_data.values.sum, precision: 2) %></p>
25
+ <p class="text-sm text-apple-green mt-2">+12% from last month</p>
26
+ <% end %>
27
+ ...
28
+ <%= render SaasPlatform::CardComponent.new do %>
29
+ <h3 class="text-lg font-semibold mb-2">Active Users</h3>
30
+ <p class="text-3xl font-bold text-apple-blue">1,234</p>
31
+ <p class="text-sm text-apple-green mt-2">+5% from last month</p>
32
+ <% end %>
33
+
34
+ <%= render SaasPlatform::CardComponent.new do %>
35
+ <h3 class="text-lg font-semibold mb-2">System Status</h3>
36
+ <p class="text-3xl font-bold text-apple-green">Healthy</p>
37
+ <p class="text-sm text-apple-gray mt-2">All systems operational</p>
38
+ <% end %>
39
+ </div>
40
+
41
+ <div class="mt-6">
42
+ <%= render SaasPlatform::CardComponent.new(padding: :none) do %>
43
+ <div class="px-6 py-4 border-b border-gray-100 flex items-center justify-between">
44
+ <h3 class="text-lg font-semibold">Recent Orders</h3>
45
+ <a href="#" class="text-sm font-medium text-apple-blue hover:underline">View all</a>
46
+ </div>
47
+ <%= render SaasPlatform::OrderListComponent.new(orders: @recent_orders) %>
48
+ <% end %>
49
+ </div>
50
+ </div>
@@ -0,0 +1,75 @@
1
+ <div class="space-y-6">
2
+ <div class="flex items-center justify-between">
3
+ <h1 class="text-2xl font-bold text-apple-dark">Search results for "<%= @query %>"</h1>
4
+ </div>
5
+
6
+ <% if @results.any? %>
7
+ <div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
8
+ <% if @results[:users]&.any? %>
9
+ <%= render SaasPlatform::CardComponent.new do %>
10
+ <h2 class="text-lg font-semibold mb-4 flex items-center">
11
+ <svg class="w-5 h-5 mr-2 text-apple-blue" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"></path></svg>
12
+ Users
13
+ </h2>
14
+ <ul class="divide-y divide-gray-100">
15
+ <% @results[:users].each do |user| %>
16
+ <li class="py-3 flex items-center justify-between">
17
+ <div>
18
+ <p class="text-sm font-medium text-apple-dark"><%= user.name %></p>
19
+ <p class="text-xs text-apple-gray"><%= user.email %></p>
20
+ </div>
21
+ <span class="px-2 py-0.5 rounded-full text-[10px] font-medium bg-apple-light text-apple-gray"><%= user.status.humanize %></span>
22
+ </li>
23
+ <% end %>
24
+ </ul>
25
+ <% end %>
26
+ <% end %>
27
+
28
+ <% if @results[:products]&.any? %>
29
+ <%= render SaasPlatform::CardComponent.new do %>
30
+ <h2 class="text-lg font-semibold mb-4 flex items-center">
31
+ <svg class="w-5 h-5 mr-2 text-apple-green" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"></path></svg>
32
+ Products
33
+ </h2>
34
+ <ul class="divide-y divide-gray-100">
35
+ <% @results[:products].each do |product| %>
36
+ <li class="py-3 flex items-center justify-between">
37
+ <div>
38
+ <p class="text-sm font-medium text-apple-dark"><%= product.name %></p>
39
+ <p class="text-xs text-apple-gray"><%= product.price.format %></p>
40
+ </div>
41
+ <span class="px-2 py-0.5 rounded-full text-[10px] font-medium bg-apple-light text-apple-gray"><%= product.stock_quantity %> in stock</span>
42
+ </li>
43
+ <% end %>
44
+ </ul>
45
+ <% end %>
46
+ <% end %>
47
+
48
+ <% if @results[:orders]&.any? %>
49
+ <%= render SaasPlatform::CardComponent.new do %>
50
+ <h2 class="text-lg font-semibold mb-4 flex items-center">
51
+ <svg class="w-5 h-5 mr-2 text-apple-orange" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"></path></svg>
52
+ Orders
53
+ </h2>
54
+ <ul class="divide-y divide-gray-100">
55
+ <% @results[:orders].each do |order| %>
56
+ <li class="py-3 flex items-center justify-between">
57
+ <div>
58
+ <p class="text-sm font-medium text-apple-dark">Order #<%= order.id %></p>
59
+ <p class="text-xs text-apple-gray"><%= order.created_at.strftime("%b %d, %Y") %></p>
60
+ </div>
61
+ <span class="px-2 py-0.5 rounded-full text-[10px] font-medium bg-apple-light text-apple-gray"><%= order.total.format %></span>
62
+ </li>
63
+ <% end %>
64
+ </ul>
65
+ <% end %>
66
+ <% end %>
67
+ </div>
68
+ <% else %>
69
+ <%= render SaasPlatform::CardComponent.new(class: "text-center py-12") do %>
70
+ <svg class="mx-auto h-12 w-12 text-apple-gray" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
71
+ <h3 class="mt-2 text-sm font-medium text-apple-dark">No results found</h3>
72
+ <p class="mt-1 text-sm text-apple-gray">Try adjusting your search or filters.</p>
73
+ <% end %>
74
+ <% end %>
75
+ </div>