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,43 @@
1
+ module SaasPlatform
2
+ class Order < ApplicationRecord
3
+ has_paper_trail
4
+ include AASM
5
+ acts_as_tenant :account, class_name: 'SaasPlatform::Account'
6
+
7
+ belongs_to :user, class_name: 'SaasPlatform::User'
8
+ has_many :items, class_name: 'SaasPlatform::OrderItem', dependent: :destroy
9
+ has_many :transactions, as: :reference, class_name: 'SaasPlatform::Transaction'
10
+
11
+ monetize :total_cents
12
+
13
+ aasm column: :status do
14
+ state :pending, initial: true
15
+ state :paid
16
+ state :shipped
17
+ state :completed
18
+ state :cancelled
19
+ state :refunded
20
+
21
+ event :pay do
22
+ transitions from: :pending, to: :paid
23
+ end
24
+
25
+ event :ship do
26
+ transitions from: :paid, to: :shipped
27
+ end
28
+
29
+ event :complete do
30
+ transitions from: :shipped, to: :completed
31
+ end
32
+
33
+ event :cancel do
34
+ transitions from: [:pending, :paid], to: :cancelled
35
+ end
36
+ end
37
+
38
+ def calculate_total!
39
+ self.total = items.sum { |item| item.unit_price * item.quantity }
40
+ save!
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,18 @@
1
+ module SaasPlatform
2
+ class OrderItem < ApplicationRecord
3
+ belongs_to :order, class_name: 'SaasPlatform::Order'
4
+ belongs_to :product, class_name: 'SaasPlatform::Product'
5
+
6
+ monetize :unit_price_cents
7
+
8
+ validates :quantity, presence: true, numericality: { greater_than: 0 }
9
+
10
+ before_validation :set_unit_price, on: :create
11
+
12
+ private
13
+
14
+ def set_unit_price
15
+ self.unit_price = product.price if product
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ module SaasPlatform
2
+ class Product < ApplicationRecord
3
+ include PgSearch::Model
4
+ pg_search_scope :search_by_name, against: [:name, :description]
5
+
6
+ acts_as_tenant :account, class_name: 'SaasPlatform::Account'
7
+
8
+ monetize :price_cents
9
+
10
+ validates :name, presence: true
11
+ validates :price_cents, numericality: { greater_than_or_equal_to: 0 }
12
+
13
+ scope :active, -> { where(status: 'active') }
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module SaasPlatform
2
+ class Role < ApplicationRecord
3
+ has_and_belongs_to_many :users, join_table: :saas_platform_users_saas_platform_roles
4
+
5
+ belongs_to :resource,
6
+ polymorphic: true,
7
+ optional: true
8
+
9
+ validates :resource_type,
10
+ inclusion: { in: Rolify.resource_types },
11
+ allow_nil: true
12
+
13
+ scopify
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module SaasPlatform
2
+ class Transaction < ApplicationRecord
3
+ belongs_to :wallet, class_name: 'SaasPlatform::Wallet'
4
+ belongs_to :reference, polymorphic: true, optional: true
5
+
6
+ monetize :amount_cents
7
+
8
+ validates :transaction_type, inclusion: { in: %w[credit debit] }
9
+ validates :status, presence: true
10
+
11
+ scope :recent, -> { order(created_at: :desc) }
12
+ scope :credits, -> { where(transaction_type: 'credit') }
13
+ scope :debits, -> { where(transaction_type: 'debit') }
14
+ end
15
+ end
@@ -0,0 +1,52 @@
1
+ module SaasPlatform
2
+ class User < ApplicationRecord
3
+ include PgSearch::Model
4
+ pg_search_scope :search_by_name, against: [:first_name, :last_name, :email]
5
+
6
+ acts_as_tenant :account, class_name: 'SaasPlatform::Account'
7
+ rolify role_cname: 'SaasPlatform::Role'
8
+
9
+ # Include default devise modules. Others available are:
10
+ # :omniauthable
11
+ devise :two_factor_authenticatable, :database_authenticatable, :registerable,
12
+ :recoverable, :rememberable, :validatable, :confirmable, :lockable,
13
+ :trackable, :omniauthable,
14
+ otp_secret_encryption_key: ENV['OTP_SECRET_ENCRYPTION_KEY']
15
+
16
+ # Validations
17
+ validates :first_name, presence: true
18
+ validates :last_name, presence: true
19
+
20
+ # Scopes
21
+ scope :active, -> { where(status: 'active', deleted_at: nil) }
22
+ scope :deleted, -> { where.not(deleted_at: nil) }
23
+
24
+ def name
25
+ "#{first_name} #{last_name}"
26
+ end
27
+
28
+ def super_admin?
29
+ has_role?(:super_admin)
30
+ end
31
+
32
+ def admin?
33
+ has_role?(:admin) || super_admin?
34
+ end
35
+
36
+ def manager?
37
+ has_role?(:manager) || admin?
38
+ end
39
+
40
+ def staff?
41
+ has_role?(:staff) || manager?
42
+ end
43
+
44
+ def soft_delete
45
+ update(deleted_at: Time.current, status: 'deleted')
46
+ end
47
+
48
+ def active_for_authentication?
49
+ super && deleted_at.nil? && status == 'active'
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,38 @@
1
+ module SaasPlatform
2
+ class Wallet < ApplicationRecord
3
+ has_paper_trail
4
+ belongs_to :account, class_name: 'SaasPlatform::Account'
5
+ has_many :transactions, class_name: 'SaasPlatform::Transaction', dependent: :destroy
6
+
7
+ register_currency :usd
8
+ monetize :balance_cents
9
+
10
+ validates :currency, presence: true
11
+
12
+ def credit!(amount, category: nil, reference: nil, metadata: {})
13
+ transactions.create!(
14
+ amount: amount,
15
+ transaction_type: 'credit',
16
+ category: category,
17
+ reference: reference,
18
+ metadata: metadata,
19
+ status: 'completed'
20
+ )
21
+ update!(balance: balance + amount)
22
+ end
23
+
24
+ def debit!(amount, category: nil, reference: nil, metadata: {})
25
+ raise "Insufficient funds" if balance < amount
26
+
27
+ transactions.create!(
28
+ amount: amount,
29
+ transaction_type: 'debit',
30
+ category: category,
31
+ reference: reference,
32
+ metadata: metadata,
33
+ status: 'completed'
34
+ )
35
+ update!(balance: balance - amount)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,18 @@
1
+ module SaasPlatform
2
+ class WebhookEndpoint < ApplicationRecord
3
+ acts_as_tenant :account, class_name: 'SaasPlatform::Account'
4
+
5
+ validates :url, presence: true, format: { with: URI::DEFAULT_PARSER.make_regexp(['http', 'https']) }
6
+ validates :secret, presence: true
7
+
8
+ before_validation :generate_secret, on: :create
9
+
10
+ scope :active, -> { where(active: true) }
11
+
12
+ private
13
+
14
+ def generate_secret
15
+ self.secret = "whsec_#{SecureRandom.hex(24)}"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ module SaasPlatform
2
+ class WebhookEvent < ApplicationRecord
3
+ acts_as_tenant :account, class_name: 'SaasPlatform::Account'
4
+
5
+ belongs_to :webhook_endpoint, class_name: 'SaasPlatform::WebhookEndpoint'
6
+
7
+ validates :event_type, presence: true
8
+ validates :payload, presence: true
9
+ end
10
+ end
@@ -0,0 +1,53 @@
1
+ module SaasPlatform
2
+ class ApplicationPolicy
3
+ attr_reader :user, :record
4
+
5
+ def initialize(user, record)
6
+ @user = user
7
+ @record = record
8
+ end
9
+
10
+ def index?
11
+ false
12
+ end
13
+
14
+ def show?
15
+ false
16
+ end
17
+
18
+ def create?
19
+ false
20
+ end
21
+
22
+ def new?
23
+ create?
24
+ end
25
+
26
+ def update?
27
+ false
28
+ end
29
+
30
+ def edit?
31
+ update?
32
+ end
33
+
34
+ def destroy?
35
+ false
36
+ end
37
+
38
+ class Scope
39
+ def initialize(user, scope)
40
+ @user = user
41
+ @scope = scope
42
+ end
43
+
44
+ def resolve
45
+ scope.all
46
+ end
47
+
48
+ private
49
+
50
+ attr_reader :user, :scope
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,29 @@
1
+ module SaasPlatform
2
+ class AnalyticsService
3
+ def initialize(account)
4
+ @account = account
5
+ end
6
+
7
+ def revenue_over_time(period: :day, last: 30)
8
+ @account.orders.paid
9
+ .group_by_period(period, :created_at, last: last)
10
+ .sum(:total_cents)
11
+ .transform_values { |v| v / 100.0 }
12
+ end
13
+
14
+ def top_products(limit: 5)
15
+ SaasPlatform::OrderItem.joins(:order)
16
+ .where(saas_platform_orders: { account_id: @account.id, status: 'paid' })
17
+ .joins(:product)
18
+ .group('saas_platform_products.name')
19
+ .sum(:quantity)
20
+ .sort_by { |_k, v| -v }
21
+ .first(limit)
22
+ .to_h
23
+ end
24
+
25
+ def user_growth(period: :week, last: 12)
26
+ @account.users.group_by_period(period, :created_at, last: last).count
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ module SaasPlatform
2
+ class PaymentService
3
+ def initialize(account)
4
+ @account = account
5
+ @wallet = account.wallet || account.create_wallet(currency: 'USD')
6
+ end
7
+
8
+ def process_order_payment(order, stripe_payment_intent_id)
9
+ # In a real app, verify with Stripe
10
+ # stripe_payload = Stripe::PaymentIntent.retrieve(stripe_payment_intent_id)
11
+
12
+ Order.transaction do
13
+ order.pay!
14
+ @wallet.credit!(order.total, category: 'sale', reference: order)
15
+
16
+ # Log audit trail
17
+ @account.notifications.create(
18
+ message: "Payment of #{order.total.format} received for Order ##{order.id}",
19
+ category: 'payment'
20
+ )
21
+ end
22
+ end
23
+
24
+ def withdraw_funds(amount)
25
+ raise "Insufficient balance" if @wallet.balance < amount
26
+
27
+ @wallet.debit!(amount, category: 'withdrawal', status: 'pending')
28
+ # Trigger background job for payout
29
+ # PayoutJob.perform_later(@account.id, amount.cents)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,45 @@
1
+ module SaasPlatform
2
+ class PlanService
3
+ PLANS = {
4
+ 'free' => {
5
+ name: 'Free',
6
+ max_users: 2,
7
+ max_products: 5,
8
+ features: [:basic_analytics]
9
+ },
10
+ 'pro' => {
11
+ name: 'Pro',
12
+ max_users: 10,
13
+ max_products: 100,
14
+ features: [:basic_analytics, :advanced_analytics, :api_access]
15
+ },
16
+ 'enterprise' => {
17
+ name: 'Enterprise',
18
+ max_users: 1000,
19
+ max_products: 10000,
20
+ features: [:basic_analytics, :advanced_analytics, :api_access, :webhooks, :audit_logs]
21
+ }
22
+ }.freeze
23
+
24
+ def initialize(account)
25
+ @account = account
26
+ @plan_config = PLANS[account.plan_name] || PLANS['free']
27
+ end
28
+
29
+ def can_add_user?
30
+ @account.users.count < @plan_config[:max_users]
31
+ end
32
+
33
+ def can_add_product?
34
+ @account.products.count < @plan_config[:max_products]
35
+ end
36
+
37
+ def has_feature?(feature)
38
+ @plan_config[:features].include?(feature.to_sym)
39
+ end
40
+
41
+ def plan_details
42
+ @plan_config
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,20 @@
1
+ module SaasPlatform
2
+ class SearchService
3
+ def initialize(account, query)
4
+ @account = account
5
+ @query = query
6
+ end
7
+
8
+ def perform
9
+ return {} if @query.blank?
10
+
11
+ ActsAsTenant.with_tenant(@account) do
12
+ {
13
+ users: SaasPlatform::User.search_by_name(@query).limit(5),
14
+ products: SaasPlatform::Product.search_by_name(@query).limit(5),
15
+ orders: SaasPlatform::Order.where(id: @query.to_i).limit(5) # Simple ID search for orders
16
+ }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,59 @@
1
+ <% content_for :header do %>
2
+ Create your account
3
+ <% end %>
4
+
5
+ <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { class: "space-y-6" }) do |f| %>
6
+ <%= render "devise/shared/error_messages", resource: resource %>
7
+
8
+ <div class="grid grid-cols-2 gap-4">
9
+ <div>
10
+ <%= f.label :first_name, class: "block text-sm font-medium text-apple-gray" %>
11
+ <div class="mt-1">
12
+ <%= f.text_field :first_name, autofocus: true, 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" %>
13
+ </div>
14
+ </div>
15
+ <div>
16
+ <%= f.label :last_name, class: "block text-sm font-medium text-apple-gray" %>
17
+ <div class="mt-1">
18
+ <%= f.text_field :last_name, 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" %>
19
+ </div>
20
+ </div>
21
+ </div>
22
+
23
+ <div>
24
+ <%= f.label :email, class: "block text-sm font-medium text-apple-gray" %>
25
+ <div class="mt-1">
26
+ <%= f.email_field :email, 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" %>
27
+ </div>
28
+ </div>
29
+
30
+ <div>
31
+ <%= f.label :password, class: "block text-sm font-medium text-apple-gray" %>
32
+ <% if @minimum_password_length %>
33
+ <em class="text-xs text-apple-gray">(<%= @minimum_password_length %> characters minimum)</em>
34
+ <% end %>
35
+ <div class="mt-1">
36
+ <%= f.password_field :password, autocomplete: "new-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" %>
37
+ </div>
38
+ </div>
39
+
40
+ <div>
41
+ <%= f.label :password_confirmation, class: "block text-sm font-medium text-apple-gray" %>
42
+ <div class="mt-1">
43
+ <%= f.password_field :password_confirmation, autocomplete: "new-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" %>
44
+ </div>
45
+ </div>
46
+
47
+ <div>
48
+ <%= render SaasPlatform::ButtonComponent.new(variant: :primary, type: :submit, class: "w-full") do %>
49
+ Create Account
50
+ <% end %>
51
+ </div>
52
+ <% end %>
53
+
54
+ <% content_for :footer do %>
55
+ <p class="text-sm text-apple-gray">
56
+ Already have an account?
57
+ <%= link_to "Sign in", new_session_path(resource_name), class: "font-medium text-apple-blue hover:text-blue-500" %>
58
+ </p>
59
+ <% end %>
@@ -0,0 +1,16 @@
1
+ <h2>Resend confirmation instructions</h2>
2
+
3
+ <%= form_for(resource, as: resource_name, url: confirmation_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", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %>
9
+ </div>
10
+
11
+ <div class="actions">
12
+ <%= f.submit "Resend confirmation instructions" %>
13
+ </div>
14
+ <% end %>
15
+
16
+ <%= render "saas_platform_users/shared/links" %>
@@ -0,0 +1,5 @@
1
+ <p>Welcome <%= @email %>!</p>
2
+
3
+ <p>You can confirm your account email through the link below:</p>
4
+
5
+ <p><%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %></p>
@@ -0,0 +1,7 @@
1
+ <p>Hello <%= @email %>!</p>
2
+
3
+ <% if @resource.try(:unconfirmed_email?) %>
4
+ <p>We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.</p>
5
+ <% else %>
6
+ <p>We're contacting you to notify you that your email has been changed to <%= @resource.email %>.</p>
7
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <p>Hello <%= @resource.email %>!</p>
2
+
3
+ <p>We're contacting you to notify you that your password has been changed.</p>
@@ -0,0 +1,8 @@
1
+ <p>Hello <%= @resource.email %>!</p>
2
+
3
+ <p>Someone has requested a link to change your password. You can do this through the link below.</p>
4
+
5
+ <p><%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %></p>
6
+
7
+ <p>If you didn't request this, please ignore this email.</p>
8
+ <p>Your password won't change until you access the link above and create a new one.</p>
@@ -0,0 +1,7 @@
1
+ <p>Hello <%= @resource.email %>!</p>
2
+
3
+ <p>Your account has been locked due to an excessive number of unsuccessful sign in attempts.</p>
4
+
5
+ <p>Click the link below to unlock your account:</p>
6
+
7
+ <p><%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %></p>
@@ -0,0 +1,25 @@
1
+ <h2>Change your password</h2>
2
+
3
+ <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %>
4
+ <%= render "saas_platform_users/shared/error_messages", resource: resource %>
5
+ <%= f.hidden_field :reset_password_token %>
6
+
7
+ <div class="field">
8
+ <%= f.label :password, "New password" %><br />
9
+ <% if @minimum_password_length %>
10
+ <em>(<%= @minimum_password_length %> characters minimum)</em><br />
11
+ <% end %>
12
+ <%= f.password_field :password, autofocus: true, autocomplete: "new-password" %>
13
+ </div>
14
+
15
+ <div class="field">
16
+ <%= f.label :password_confirmation, "Confirm new password" %><br />
17
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
18
+ </div>
19
+
20
+ <div class="actions">
21
+ <%= f.submit "Change my password" %>
22
+ </div>
23
+ <% end %>
24
+
25
+ <%= render "saas_platform_users/shared/links" %>
@@ -0,0 +1,16 @@
1
+ <h2>Forgot your password?</h2>
2
+
3
+ <%= form_for(resource, as: resource_name, url: password_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 "Send me reset password instructions" %>
13
+ </div>
14
+ <% end %>
15
+
16
+ <%= render "saas_platform_users/shared/links" %>
@@ -0,0 +1,43 @@
1
+ <h2>Edit <%= resource_name.to_s.humanize %></h2>
2
+
3
+ <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) 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
+ <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
12
+ <div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
13
+ <% end %>
14
+
15
+ <div class="field">
16
+ <%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
17
+ <%= f.password_field :password, autocomplete: "new-password" %>
18
+ <% if @minimum_password_length %>
19
+ <br />
20
+ <em><%= @minimum_password_length %> characters minimum</em>
21
+ <% end %>
22
+ </div>
23
+
24
+ <div class="field">
25
+ <%= f.label :password_confirmation %><br />
26
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
27
+ </div>
28
+
29
+ <div class="field">
30
+ <%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
31
+ <%= f.password_field :current_password, autocomplete: "current-password" %>
32
+ </div>
33
+
34
+ <div class="actions">
35
+ <%= f.submit "Update" %>
36
+ </div>
37
+ <% end %>
38
+
39
+ <h3>Cancel my account</h3>
40
+
41
+ <div>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?" }, method: :delete %></div>
42
+
43
+ <%= link_to "Back", :back %>
@@ -0,0 +1,29 @@
1
+ <h2>Sign up</h2>
2
+
3
+ <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) 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="field">
12
+ <%= f.label :password %>
13
+ <% if @minimum_password_length %>
14
+ <em>(<%= @minimum_password_length %> characters minimum)</em>
15
+ <% end %><br />
16
+ <%= f.password_field :password, autocomplete: "new-password" %>
17
+ </div>
18
+
19
+ <div class="field">
20
+ <%= f.label :password_confirmation %><br />
21
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
22
+ </div>
23
+
24
+ <div class="actions">
25
+ <%= f.submit "Sign up" %>
26
+ </div>
27
+ <% end %>
28
+
29
+ <%= render "saas_platform_users/shared/links" %>