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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +143 -0
- data/Rakefile +14 -0
- data/app/assets/stylesheets/saas_platform/application.css +15 -0
- data/app/assets/stylesheets/saas_platform/application.tailwind.css +25 -0
- data/app/components/saas_platform/button_component.rb +46 -0
- data/app/components/saas_platform/card_component.html.erb +3 -0
- data/app/components/saas_platform/card_component.rb +29 -0
- data/app/components/saas_platform/notification_component.html.erb +27 -0
- data/app/components/saas_platform/notification_component.rb +11 -0
- data/app/components/saas_platform/order_list_component.html.erb +28 -0
- data/app/components/saas_platform/order_list_component.rb +18 -0
- data/app/controllers/saas_platform/application_controller.rb +31 -0
- data/app/controllers/saas_platform/audit_logs_controller.rb +22 -0
- data/app/controllers/saas_platform/dashboard_controller.rb +14 -0
- data/app/controllers/saas_platform/devise/confirmations_controller.rb +7 -0
- data/app/controllers/saas_platform/devise/passwords_controller.rb +7 -0
- data/app/controllers/saas_platform/devise/registrations_controller.rb +7 -0
- data/app/controllers/saas_platform/devise/sessions_controller.rb +7 -0
- data/app/controllers/saas_platform/devise/unlocks_controller.rb +7 -0
- data/app/controllers/saas_platform/search_controller.rb +15 -0
- data/app/helpers/saas_platform/application_helper.rb +4 -0
- data/app/jobs/saas_platform/application_job.rb +4 -0
- data/app/mailers/saas_platform/application_mailer.rb +6 -0
- data/app/models/saas_platform/account.rb +36 -0
- data/app/models/saas_platform/api_key.rb +24 -0
- data/app/models/saas_platform/application_record.rb +5 -0
- data/app/models/saas_platform/notification.rb +19 -0
- data/app/models/saas_platform/order.rb +43 -0
- data/app/models/saas_platform/order_item.rb +18 -0
- data/app/models/saas_platform/product.rb +15 -0
- data/app/models/saas_platform/role.rb +15 -0
- data/app/models/saas_platform/transaction.rb +15 -0
- data/app/models/saas_platform/user.rb +52 -0
- data/app/models/saas_platform/wallet.rb +38 -0
- data/app/models/saas_platform/webhook_endpoint.rb +18 -0
- data/app/models/saas_platform/webhook_event.rb +10 -0
- data/app/policies/saas_platform/application_policy.rb +53 -0
- data/app/services/saas_platform/analytics_service.rb +29 -0
- data/app/services/saas_platform/payment_service.rb +32 -0
- data/app/services/saas_platform/plan_service.rb +45 -0
- data/app/services/saas_platform/search_service.rb +20 -0
- data/app/views/devise/registrations/new.html.erb +59 -0
- data/app/views/devise/saas_platform_users/confirmations/new.html.erb +16 -0
- data/app/views/devise/saas_platform_users/mailer/confirmation_instructions.html.erb +5 -0
- data/app/views/devise/saas_platform_users/mailer/email_changed.html.erb +7 -0
- data/app/views/devise/saas_platform_users/mailer/password_change.html.erb +3 -0
- data/app/views/devise/saas_platform_users/mailer/reset_password_instructions.html.erb +8 -0
- data/app/views/devise/saas_platform_users/mailer/unlock_instructions.html.erb +7 -0
- data/app/views/devise/saas_platform_users/passwords/edit.html.erb +25 -0
- data/app/views/devise/saas_platform_users/passwords/new.html.erb +16 -0
- data/app/views/devise/saas_platform_users/registrations/edit.html.erb +43 -0
- data/app/views/devise/saas_platform_users/registrations/new.html.erb +29 -0
- data/app/views/devise/saas_platform_users/sessions/new.html.erb +26 -0
- data/app/views/devise/saas_platform_users/shared/_error_messages.html.erb +15 -0
- data/app/views/devise/saas_platform_users/shared/_links.html.erb +25 -0
- data/app/views/devise/saas_platform_users/unlocks/new.html.erb +16 -0
- data/app/views/devise/sessions/new.html.erb +47 -0
- data/app/views/layouts/saas_platform/application.html.erb +17 -0
- data/app/views/layouts/saas_platform/auth.html.erb +48 -0
- data/app/views/layouts/saas_platform/dashboard.html.erb +76 -0
- data/app/views/saas_platform/audit_logs/index.html.erb +43 -0
- data/app/views/saas_platform/dashboard/index.html.erb +50 -0
- data/app/views/saas_platform/search/index.html.erb +75 -0
- data/config/initializers/devise.rb +313 -0
- data/config/initializers/money.rb +115 -0
- data/config/initializers/rack_attack.rb +27 -0
- data/config/locales/devise.en.yml +65 -0
- data/config/routes.rb +16 -0
- data/config/tailwind.config.js +45 -0
- data/db/migrate/20260603000000_devise_create_saas_platform_users.rb +53 -0
- data/db/migrate/20260603000001_rolify_create_saas_platform_roles.rb +19 -0
- data/db/migrate/20260603000002_create_saas_platform_accounts.rb +20 -0
- data/db/migrate/20260603000003_create_saas_platform_banking.rb +28 -0
- data/db/migrate/20260603000004_create_saas_platform_ecommerce.rb +37 -0
- data/db/migrate/20260603000005_create_versions.rb +14 -0
- data/db/migrate/20260603000006_create_saas_platform_api_keys.rb +17 -0
- data/db/migrate/20260603000007_create_saas_platform_webhooks.rb +25 -0
- data/db/migrate/20260603000008_create_saas_platform_notifications.rb +14 -0
- data/db/migrate/20260603000009_add_subscription_to_saas_platform_accounts.rb +12 -0
- data/lib/saas_platform/engine.rb +5 -0
- data/lib/saas_platform/version.rb +3 -0
- data/lib/saas_platform.rb +27 -0
- data/lib/tasks/saas_platform_tasks.rake +4 -0
- 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,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,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" %>
|