belt 0.2.19 → 0.3.1

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.

Potentially problematic release.


This version of belt might be problematic. Click here for more details.

data/lib/belt/cli.rb CHANGED
@@ -6,6 +6,7 @@ require_relative 'cli/env_resolver'
6
6
  require_relative 'cli/new_command'
7
7
  require_relative 'cli/generate_command'
8
8
  require_relative 'cli/destroy_command'
9
+ require_relative 'cli/index_command'
9
10
  require_relative 'cli/frontend_command'
10
11
  require_relative 'cli/frontend_setup_command'
11
12
  require_relative 'cli/frontend_deploy_command'
@@ -42,6 +42,14 @@ module Belt
42
42
  word.to_s.split('_').map(&:capitalize).join
43
43
  end
44
44
 
45
+ # "conversation_id" → "conversationId"
46
+ def camelize_lower(word)
47
+ parts = word.to_s.split('_')
48
+ return word if parts.empty?
49
+
50
+ parts.first + parts[1..].map(&:capitalize).join
51
+ end
52
+
45
53
  # "BlogPost" → "blog_post"
46
54
  def underscore(word)
47
55
  word.to_s
@@ -52,48 +52,46 @@ module Belt
52
52
 
53
53
  class NestedResourceBuilder
54
54
  def initialize(gateway, prefix, collection_prefix, inherited_tables: [], inherited_auth: nil, # rubocop:disable Metrics/ParameterLists
55
- inherited_controller: nil)
55
+ inherited_controller: nil, inherited_lambda: nil)
56
56
  @gateway = gateway
57
57
  @prefix = prefix
58
58
  @collection_prefix = collection_prefix
59
59
  @inherited_tables = inherited_tables
60
60
  @inherited_auth = inherited_auth
61
61
  @inherited_controller = inherited_controller
62
+ @inherited_lambda = inherited_lambda
62
63
  end
63
64
 
64
- def resources(name, options = {})
65
+ def resources(name, options = {}, &block)
65
66
  resource_name = name.to_s
66
67
  singular = @gateway.send(:singularize, resource_name)
67
68
  param_name = options[:param] || "#{singular}_id"
69
+ options = options.merge(tables: [resource_name.to_sym]) unless options.key?(:tables)
68
70
  options = merge_inherited_options(options)
69
- options = @gateway.send(:auto_infer_tables, resource_name, options)
70
71
  resource_options = options.merge(route_type: :resources)
71
72
  actions = @gateway.send(:determine_actions, options)
72
73
 
73
- @gateway.send(:add_route, :get, "#{@prefix}/#{resource_name}", resource_options) if actions.include?(:index)
74
- @gateway.send(:add_route, :post, "#{@prefix}/#{resource_name}", resource_options) if actions.include?(:create)
75
- if actions.include?(:show)
76
- @gateway.send(:add_route, :get, "#{@prefix}/#{resource_name}/{#{param_name}}",
77
- resource_options)
78
- end
79
- if actions.include?(:update)
80
- @gateway.send(:add_route, :put, "#{@prefix}/#{resource_name}/{#{param_name}}",
81
- resource_options)
82
- end
83
- return unless actions.include?(:destroy)
74
+ add_nested_resource_routes(resource_name, param_name, resource_options, actions)
75
+ return unless block
84
76
 
85
- @gateway.send(:add_route, :delete, "#{@prefix}/#{resource_name}/{#{param_name}}",
86
- resource_options)
77
+ nested_member_prefix = "#{@prefix}/#{resource_name}/{#{param_name}}"
78
+ nested_collection_prefix = "#{@prefix}/#{resource_name}"
79
+ nested_builder = NestedResourceBuilder.new(@gateway, nested_member_prefix, nested_collection_prefix,
80
+ inherited_tables: Array(options[:tables] || []),
81
+ inherited_auth: options[:auth] || @inherited_auth,
82
+ inherited_controller: @inherited_controller,
83
+ inherited_lambda: @inherited_lambda)
84
+ nested_builder.instance_eval(&block)
87
85
  end
88
86
 
89
87
  def member(&)
90
88
  MemberCollectionBuilder.new(@gateway, @prefix, @inherited_tables, @inherited_auth,
91
- @inherited_controller).instance_eval(&)
89
+ @inherited_controller, @inherited_lambda).instance_eval(&)
92
90
  end
93
91
 
94
92
  def collection(&)
95
93
  MemberCollectionBuilder.new(@gateway, @collection_prefix, @inherited_tables,
96
- @inherited_auth, @inherited_controller).instance_eval(&)
94
+ @inherited_auth, @inherited_controller, @inherited_lambda).instance_eval(&)
97
95
  end
98
96
 
99
97
  %i[get post put delete patch].each do |method|
@@ -107,6 +105,20 @@ module Belt
107
105
 
108
106
  private
109
107
 
108
+ def add_nested_resource_routes(resource_name, param_name, resource_options, actions)
109
+ @gateway.send(:add_route, :get, "#{@prefix}/#{resource_name}", resource_options) if actions.include?(:index)
110
+ @gateway.send(:add_route, :post, "#{@prefix}/#{resource_name}", resource_options) if actions.include?(:create)
111
+ if actions.include?(:show)
112
+ @gateway.send(:add_route, :get, "#{@prefix}/#{resource_name}/{#{param_name}}", resource_options)
113
+ end
114
+ if actions.include?(:update)
115
+ @gateway.send(:add_route, :put, "#{@prefix}/#{resource_name}/{#{param_name}}", resource_options)
116
+ end
117
+ return unless actions.include?(:destroy)
118
+
119
+ @gateway.send(:add_route, :delete, "#{@prefix}/#{resource_name}/{#{param_name}}", resource_options)
120
+ end
121
+
110
122
  def merge_inherited_options(options)
111
123
  result = options.dup
112
124
  if @inherited_tables.any?
@@ -115,17 +127,20 @@ module Belt
115
127
  end
116
128
  result[:auth] ||= @inherited_auth if @inherited_auth
117
129
  result[:controller] ||= @inherited_controller if @inherited_controller
130
+ result[:lambda] ||= @inherited_lambda if @inherited_lambda
118
131
  result
119
132
  end
120
133
  end
121
134
 
122
135
  class MemberCollectionBuilder
123
- def initialize(gateway, prefix, inherited_tables, inherited_auth, inherited_controller = nil)
136
+ def initialize(gateway, prefix, inherited_tables, inherited_auth, # rubocop:disable Metrics/ParameterLists
137
+ inherited_controller = nil, inherited_lambda = nil)
124
138
  @gateway = gateway
125
139
  @prefix = prefix
126
140
  @inherited_tables = inherited_tables
127
141
  @inherited_auth = inherited_auth
128
142
  @inherited_controller = inherited_controller
143
+ @inherited_lambda = inherited_lambda
129
144
  end
130
145
 
131
146
  %i[get post put delete patch].each do |method|
@@ -146,6 +161,7 @@ module Belt
146
161
  end
147
162
  result[:auth] ||= @inherited_auth if @inherited_auth
148
163
  result[:controller] ||= @inherited_controller if @inherited_controller
164
+ result[:lambda] ||= @inherited_lambda if @inherited_lambda
149
165
  result
150
166
  end
151
167
  end
@@ -197,9 +213,11 @@ module Belt
197
213
  resource_tables = Array(options[:tables] || [])
198
214
  inherited_tables = (@default_tables + resource_tables).uniq
199
215
  inherited_auth = options[:auth] || @default_auth
216
+ inherited_lambda = options[:lambda]
200
217
  nested_builder = NestedResourceBuilder.new(self, member_prefix, collection_prefix,
201
218
  inherited_tables: inherited_tables,
202
- inherited_auth: inherited_auth)
219
+ inherited_auth: inherited_auth,
220
+ inherited_lambda: inherited_lambda)
203
221
  nested_builder.instance_eval(&)
204
222
  end
205
223
 
@@ -282,11 +300,15 @@ module Belt
282
300
  @dsl
283
301
  end
284
302
 
285
- def namespace(name, options = {}, &)
286
- gateway = Belt::ApiGateway.new(name, options)
287
- RouteBuilder.new(gateway).instance_eval(&) if block_given?
288
- @dsl.api_gateways << gateway
303
+ # Primary DSL keyword: defines an API Gateway with a default Lambda function.
304
+ def gateway(name, options = {}, &)
305
+ gw = Belt::ApiGateway.new(name, options)
306
+ RouteBuilder.new(gw).instance_eval(&) if block_given?
307
+ @dsl.api_gateways << gw
289
308
  end
309
+
310
+ # Legacy alias — existing routes files using `namespace` still work.
311
+ alias namespace gateway
290
312
  end
291
313
 
292
314
  def routes
@@ -302,11 +324,40 @@ module Belt
302
324
  @gateway = gateway
303
325
  @scope_prefix = ''
304
326
  @scope_module = nil
327
+ @lambda_target = nil
305
328
  @scope_auth = nil
306
329
  @scope_tables = []
307
330
  @scope_controller = nil
308
331
  end
309
332
 
333
+ # Rails-like `namespace` — adds both a path prefix AND a module prefix.
334
+ # Equivalent to: scope path: "admin", module: "admin"
335
+ #
336
+ # Example:
337
+ # namespace :admin do
338
+ # resources :users # → /admin/users, controller: "admin/users"
339
+ # end
340
+ def namespace(name, options = {}, &)
341
+ segment = name.to_s
342
+ merged = { path: segment, module: segment }.merge(options)
343
+ scope(merged, &)
344
+ end
345
+
346
+ # Rails-like `scope` — groups routes with shared options (path prefix, module,
347
+ # auth, tables, controller).
348
+ #
349
+ # Examples:
350
+ # scope path: "admin" do
351
+ # resources :users # → /admin/users
352
+ # end
353
+ #
354
+ # scope module: "v2" do
355
+ # resources :users # → /users, controller: "v2/users"
356
+ # end
357
+ #
358
+ # scope path: "v1", module: "v1", auth: :cognito do
359
+ # resources :posts # → /v1/posts, controller: "v1/posts", auth: cognito
360
+ # end
310
361
  def scope(options = {}, &)
311
362
  previous_prefix = @scope_prefix
312
363
  previous_module = @scope_module
@@ -314,12 +365,16 @@ module Belt
314
365
  previous_tables = @scope_tables
315
366
  previous_controller = @scope_controller
316
367
 
317
- # Nest path segments (Rails-style): scope path: "a" { scope path: "b" } → "a/b"
368
+ # Nest path segments: scope path: "a" { scope path: "b" } → "a/b"
318
369
  if options.key?(:path)
319
370
  segment = options[:path].to_s.gsub(%r{^/|/$}, '')
320
371
  @scope_prefix = @scope_prefix.to_s.empty? ? segment : "#{@scope_prefix}/#{segment}"
321
372
  end
322
- @scope_module = options[:module] || @scope_module
373
+ # Nest module segments: namespace :admin { namespace :v2 } → "admin/v2"
374
+ if options.key?(:module)
375
+ mod_segment = options[:module].to_s
376
+ @scope_module = @scope_module.to_s.empty? ? mod_segment : "#{@scope_module}/#{mod_segment}"
377
+ end
323
378
  @scope_auth = options[:auth] || @scope_auth
324
379
  @scope_tables = (@scope_tables + Array(options[:tables] || [])).uniq
325
380
  @scope_controller = options[:controller] || @scope_controller
@@ -333,85 +388,76 @@ module Belt
333
388
  @scope_controller = previous_controller
334
389
  end
335
390
 
391
+ # Target a different Lambda function for enclosed routes.
392
+ # Routes within this block will have their :lambda field set to `name`.
393
+ # Does NOT affect path prefixes or controller module resolution.
394
+ #
395
+ # Example:
396
+ # gateway :api, auth: :cognito do
397
+ # resources :posts # → lambda: "api"
398
+ #
399
+ # function :onboarding do
400
+ # resources :stuff # → lambda: "onboarding"
401
+ # end
402
+ #
403
+ # function :custom do
404
+ # get '/blah' # → lambda: "custom"
405
+ # end
406
+ # end
407
+ def function(name, options = {}, &)
408
+ previous_lambda = @lambda_target
409
+ @lambda_target = name.to_s
410
+ # function blocks can also carry auth/tables
411
+ previous_auth = @scope_auth
412
+ previous_tables = @scope_tables
413
+ @scope_auth = options[:auth] if options[:auth]
414
+ @scope_tables = (@scope_tables + Array(options[:tables] || [])).uniq
415
+
416
+ instance_eval(&) if block_given?
417
+
418
+ @lambda_target = previous_lambda
419
+ @scope_auth = previous_auth
420
+ @scope_tables = previous_tables
421
+ end
422
+
336
423
  %i[get post put delete patch].each do |method|
337
424
  define_method(method) do |path, options = {}|
338
425
  full_path = build_path(path)
339
- route_options = options.dup
340
- route_options[:lambda] ||= @scope_module if @scope_module
341
- route_options[:auth] ||= @scope_auth if @scope_auth
342
- route_options[:controller] ||= @scope_controller if @scope_controller
343
- if @scope_tables.any? || route_options[:tables]
344
- route_options[:tables] =
345
- (@scope_tables + Array(route_options[:tables] || [])).uniq
346
- end
426
+ route_options = apply_scope_to_route(options)
347
427
  @gateway.send(method, full_path, route_options)
348
428
  end
349
429
  end
350
430
 
351
- def resources(name, options = {}, &block)
431
+ def resources(name, options = {}, &)
352
432
  options = apply_scope_options(options)
353
433
 
354
- if @scope_prefix.empty?
355
- @gateway.resources(name, options, &block)
356
- else
357
- # When inside a scope, generate routes with the prefix applied.
358
- # Also set the controller explicitly so inference resolves correctly
359
- # (e.g., scope "admin" + resources :users → controller "admin/users").
434
+ if @scope_prefix.empty? && @scope_module.nil?
435
+ @gateway.resources(name, options, &)
436
+ elsif @scope_prefix.empty?
360
437
  resource_name = name.to_s
361
- singular = Belt::Inflector.singularize(resource_name)
362
- param_name = options[:param] || "#{singular}_id"
363
- controller = "#{@scope_prefix}/#{resource_name}"
364
- resource_options = options.merge(route_type: :resources, controller: controller)
365
- actions = determine_scoped_actions(options)
366
-
367
- add_scoped_resource_routes(resource_name, param_name, resource_options, actions)
368
-
369
- if block
370
- collection_prefix = build_path("/#{resource_name}")
371
- member_prefix = build_path("/#{resource_name}/{#{param_name}}")
372
- resource_tables = Array(options[:tables] || [])
373
- inherited_tables = (@gateway.default_tables + resource_tables).uniq
374
- inherited_auth = options[:auth] || @gateway.default_auth
375
- nested_builder = NestedResourceBuilder.new(@gateway, member_prefix, collection_prefix,
376
- inherited_tables: inherited_tables,
377
- inherited_auth: inherited_auth,
378
- inherited_controller: controller)
379
- nested_builder.instance_eval(&block)
380
- end
438
+ controller = determine_scoped_controller(resource_name)
439
+ options = options.merge(controller: controller) unless options[:controller]
440
+ @gateway.resources(name, options, &)
441
+ else
442
+ build_scoped_resources(name, options, &)
381
443
  end
382
444
  end
383
445
 
384
446
  def resource(name, options = {})
385
447
  options = apply_scope_options(options)
386
448
 
387
- if @scope_prefix.empty?
449
+ if @scope_prefix.empty? && @scope_module.nil?
388
450
  @gateway.resource(name, options)
389
- else
451
+ elsif @scope_prefix.empty?
390
452
  resource_name = name.to_s
391
- controller = "#{@scope_prefix}/#{resource_name}"
392
- resource_options = options.merge(route_type: :resource, controller: controller)
393
- actions = determine_scoped_actions(options, default: %i[show update destroy create])
394
-
395
- @gateway.send(:add_route, :get, build_path("/#{resource_name}"), resource_options) if actions.include?(:show)
396
- if actions.include?(:update)
397
- @gateway.send(:add_route, :put, build_path("/#{resource_name}"),
398
- resource_options)
399
- end
400
- if actions.include?(:destroy)
401
- @gateway.send(:add_route, :delete, build_path("/#{resource_name}"),
402
- resource_options)
403
- end
404
- if actions.include?(:create)
405
- @gateway.send(:add_route, :post, build_path("/#{resource_name}"),
406
- resource_options)
407
- end
453
+ controller = determine_scoped_controller(resource_name)
454
+ options = options.merge(controller: controller) unless options[:controller]
455
+ @gateway.resource(name, options)
456
+ else
457
+ build_scoped_resource(name, options)
408
458
  end
409
459
  end
410
460
 
411
- def lambda(name, &)
412
- name
413
- end
414
-
415
461
  def mount(mountable, options = {})
416
462
  prefix = options[:at]&.to_s&.gsub(%r{^/|/$}, '') || ''
417
463
  extra_tables = Array(options[:tables] || [])
@@ -456,6 +502,16 @@ module Belt
456
502
  @scope_prefix.empty? ? path : "/#{@scope_prefix}#{path}"
457
503
  end
458
504
 
505
+ def determine_scoped_controller(resource_name)
506
+ if @scope_module && !@scope_module.empty?
507
+ "#{@scope_module}/#{resource_name}"
508
+ elsif !@scope_prefix.empty?
509
+ "#{@scope_prefix}/#{resource_name}"
510
+ else
511
+ resource_name
512
+ end
513
+ end
514
+
459
515
  def determine_scoped_actions(options, default: %i[index create show update destroy])
460
516
  if options[:only]
461
517
  Array(options[:only])
@@ -480,10 +536,65 @@ module Belt
480
536
  @gateway.send(:add_route, :delete, build_path("/#{resource_name}/{#{param_name}}"), resource_options)
481
537
  end
482
538
 
539
+ def build_scoped_resources(name, options, &block)
540
+ resource_name = name.to_s
541
+ singular = Belt::Inflector.singularize(resource_name)
542
+ param_name = options[:param] || "#{singular}_id"
543
+ controller = options[:controller] || determine_scoped_controller(resource_name)
544
+ resource_options = options.merge(route_type: :resources, controller: controller)
545
+ actions = determine_scoped_actions(options)
546
+
547
+ add_scoped_resource_routes(resource_name, param_name, resource_options, actions)
548
+ build_nested_resource_block(resource_name, param_name, options, controller, &block) if block
549
+ end
550
+
551
+ def build_nested_resource_block(resource_name, param_name, options, controller, &)
552
+ collection_prefix = build_path("/#{resource_name}")
553
+ member_prefix = build_path("/#{resource_name}/{#{param_name}}")
554
+ resource_tables = Array(options[:tables] || [])
555
+ inherited_tables = (@gateway.default_tables + resource_tables).uniq
556
+ inherited_auth = options[:auth] || @gateway.default_auth
557
+ nested_builder = NestedResourceBuilder.new(@gateway, member_prefix, collection_prefix,
558
+ inherited_tables: inherited_tables,
559
+ inherited_auth: inherited_auth,
560
+ inherited_controller: controller,
561
+ inherited_lambda: @lambda_target)
562
+ nested_builder.instance_eval(&)
563
+ end
564
+
565
+ def build_scoped_resource(name, options)
566
+ resource_name = name.to_s
567
+ controller = options[:controller] || determine_scoped_controller(resource_name)
568
+ resource_options = options.merge(route_type: :resource, controller: controller)
569
+ actions = determine_scoped_actions(options, default: %i[show update destroy create])
570
+
571
+ @gateway.send(:add_route, :get, build_path("/#{resource_name}"), resource_options) if actions.include?(:show)
572
+ @gateway.send(:add_route, :put, build_path("/#{resource_name}"), resource_options) if actions.include?(:update)
573
+ if actions.include?(:destroy)
574
+ @gateway.send(:add_route, :delete, build_path("/#{resource_name}"),
575
+ resource_options)
576
+ end
577
+ @gateway.send(:add_route, :post, build_path("/#{resource_name}"), resource_options) if actions.include?(:create)
578
+ end
579
+
580
+ def apply_scope_to_route(options)
581
+ route_options = options.dup
582
+ # Lambda target: only explicit function block affects it
583
+ route_options[:lambda] ||= @lambda_target if @lambda_target
584
+ route_options[:auth] ||= @scope_auth if @scope_auth
585
+ route_options[:controller] ||= @scope_controller if @scope_controller
586
+ if @scope_tables.any? || route_options[:tables]
587
+ route_options[:tables] =
588
+ (@scope_tables + Array(route_options[:tables] || [])).uniq
589
+ end
590
+ route_options
591
+ end
592
+
483
593
  def apply_scope_options(options)
484
594
  result = options.dup
485
595
  result[:auth] ||= @scope_auth if @scope_auth
486
- result[:lambda] ||= @scope_module if @scope_module
596
+ # Lambda target: only explicit function block affects it
597
+ result[:lambda] ||= @lambda_target if @lambda_target
487
598
  result[:controller] ||= @scope_controller if @scope_controller
488
599
  result[:tables] = (@scope_tables + Array(result[:tables] || [])).uniq if @scope_tables.any? || result[:tables]
489
600
  result
data/lib/belt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Belt
4
- VERSION = '0.2.19'
4
+ VERSION = '0.3.1'
5
5
  end
@@ -51,9 +51,7 @@ resource "aws_cognito_user_pool_client" "<%= pool[:name] %>" {
51
51
 
52
52
  explicit_auth_flows = [
53
53
  "ALLOW_USER_SRP_AUTH",
54
- <% if @signup -%>
55
54
  "ALLOW_USER_PASSWORD_AUTH",
56
- <% end -%>
57
55
  "ALLOW_REFRESH_TOKEN_AUTH"
58
56
  ]
59
57
 
@@ -1,5 +1,5 @@
1
1
  import { useState } from 'react'
2
- import { confirmSignUp } from '../lib/auth'
2
+ import { confirmSignUp } from '../../lib/auth'
3
3
 
4
4
  export default function ConfirmEmail() {
5
5
  const params = new URLSearchParams(window.location.search)
@@ -1,5 +1,6 @@
1
1
  import { useState } from 'react'
2
- import { signIn, completeNewPassword } from '../lib/auth'
2
+ import { signIn, completeNewPassword } from '../../lib/auth'
3
+ import './auth.css'
3
4
 
4
5
  export default function Login({ onLogin }) {
5
6
  const [email, setEmail] = useState('')
@@ -72,9 +73,6 @@ export default function Login({ onLogin }) {
72
73
  <button type="submit" disabled={loading}>
73
74
  {loading ? 'Signing in...' : 'Sign In'}
74
75
  </button>
75
- <p className="auth-link">
76
- Don't have an account? <a href="/signup">Sign up</a>
77
- </p>
78
76
  </form>
79
77
  </div>
80
78
  )
@@ -1,5 +1,5 @@
1
1
  import { useState } from 'react'
2
- import { signUp } from '../lib/auth'
2
+ import { signUp } from '../../lib/auth'
3
3
 
4
4
  export default function SignUp() {
5
5
  const [email, setEmail] = useState('')
@@ -0,0 +1,157 @@
1
+ .auth-page {
2
+ --auth-bg: #f6f8fa;
3
+ --auth-card-bg: #ffffff;
4
+ --auth-border: #d0d7de;
5
+ --auth-text: #1f2328;
6
+ --auth-muted: #656d76;
7
+ --auth-input-bg: #f6f8fa;
8
+ --auth-btn: #2da44e;
9
+ --auth-btn-hover: #2c974b;
10
+ --auth-btn-disabled-bg: #94d3a2;
11
+ --auth-btn-disabled-text: #fff;
12
+ --auth-error-bg: rgba(248, 81, 73, 0.08);
13
+ --auth-error-border: rgba(248, 81, 73, 0.4);
14
+ --auth-error-text: #cf222e;
15
+ --auth-link: #0969da;
16
+ --auth-focus: #0969da;
17
+ }
18
+
19
+ @media (prefers-color-scheme: dark) {
20
+ .auth-page {
21
+ --auth-bg: #0d1117;
22
+ --auth-card-bg: #161b22;
23
+ --auth-border: #30363d;
24
+ --auth-text: #e6edf3;
25
+ --auth-muted: #8b949e;
26
+ --auth-input-bg: #0d1117;
27
+ --auth-btn: #238636;
28
+ --auth-btn-hover: #2ea043;
29
+ --auth-btn-disabled-bg: #21262d;
30
+ --auth-btn-disabled-text: #484f58;
31
+ --auth-error-bg: rgba(248, 81, 73, 0.1);
32
+ --auth-error-border: rgba(248, 81, 73, 0.4);
33
+ --auth-error-text: #f85149;
34
+ --auth-link: #58a6ff;
35
+ --auth-focus: #58a6ff;
36
+ }
37
+ }
38
+
39
+ .auth-page {
40
+ min-height: 100vh;
41
+ display: flex;
42
+ align-items: center;
43
+ justify-content: center;
44
+ background: var(--auth-bg);
45
+ padding: 1rem;
46
+ }
47
+
48
+ .auth-form {
49
+ background: var(--auth-card-bg);
50
+ border: 1px solid var(--auth-border);
51
+ border-radius: 12px;
52
+ padding: 2.5rem;
53
+ width: 100%;
54
+ max-width: 380px;
55
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
56
+ }
57
+
58
+ .auth-form h2 {
59
+ color: var(--auth-text);
60
+ font-size: 1.5rem;
61
+ margin: 0 0 0.5rem;
62
+ text-align: center;
63
+ }
64
+
65
+ .auth-form p {
66
+ color: var(--auth-muted);
67
+ font-size: 0.85rem;
68
+ text-align: center;
69
+ margin: 0 0 1.5rem;
70
+ }
71
+
72
+ .auth-form input {
73
+ display: block;
74
+ width: 100%;
75
+ padding: 0.75rem 1rem;
76
+ margin-bottom: 0.75rem;
77
+ background: var(--auth-input-bg);
78
+ border: 1px solid var(--auth-border);
79
+ border-radius: 8px;
80
+ color: var(--auth-text);
81
+ font-size: 0.9rem;
82
+ outline: none;
83
+ transition: border-color 0.2s;
84
+ box-sizing: border-box;
85
+ }
86
+
87
+ .auth-form input:focus {
88
+ border-color: var(--auth-focus);
89
+ }
90
+
91
+ .auth-form input::placeholder {
92
+ color: var(--auth-muted);
93
+ }
94
+
95
+ .auth-form button {
96
+ display: block;
97
+ width: 100%;
98
+ padding: 0.75rem;
99
+ margin-top: 0.5rem;
100
+ background: var(--auth-btn);
101
+ border: none;
102
+ border-radius: 8px;
103
+ color: #fff;
104
+ font-size: 0.9rem;
105
+ font-weight: 600;
106
+ cursor: pointer;
107
+ transition: background 0.2s;
108
+ }
109
+
110
+ .auth-form button:hover {
111
+ background: var(--auth-btn-hover);
112
+ }
113
+
114
+ .auth-form button:disabled {
115
+ background: var(--auth-btn-disabled-bg);
116
+ color: var(--auth-btn-disabled-text);
117
+ cursor: not-allowed;
118
+ }
119
+
120
+ .auth-error {
121
+ background: var(--auth-error-bg);
122
+ border: 1px solid var(--auth-error-border);
123
+ border-radius: 8px;
124
+ color: var(--auth-error-text);
125
+ padding: 0.6rem 0.75rem;
126
+ font-size: 0.8rem;
127
+ margin-bottom: 1rem;
128
+ text-align: center;
129
+ }
130
+
131
+ .auth-link {
132
+ color: var(--auth-muted);
133
+ font-size: 0.8rem;
134
+ text-align: center;
135
+ margin-top: 1.25rem !important;
136
+ }
137
+
138
+ .auth-link a {
139
+ color: var(--auth-link);
140
+ text-decoration: none;
141
+ }
142
+
143
+ .auth-link a:hover {
144
+ text-decoration: underline;
145
+ }
146
+
147
+ .auth-btn {
148
+ display: inline-block;
149
+ padding: 0.6rem 1.5rem;
150
+ background: var(--auth-btn);
151
+ border-radius: 8px;
152
+ color: #fff;
153
+ text-decoration: none;
154
+ font-weight: 600;
155
+ font-size: 0.85rem;
156
+ margin-top: 1rem;
157
+ }
@@ -55,7 +55,11 @@ export async function completeNewPassword(username, newPassword, session) {
55
55
  ChallengeName: 'NEW_PASSWORD_REQUIRED',
56
56
  ClientId: CLIENT_ID,
57
57
  Session: session,
58
- ChallengeResponses: { USERNAME: username, NEW_PASSWORD: newPassword }
58
+ ChallengeResponses: {
59
+ USERNAME: username,
60
+ NEW_PASSWORD: newPassword,
61
+ 'userAttributes.email': username
62
+ }
59
63
  }))
60
64
  setTokens(response.AuthenticationResult)
61
65
  return { success: true }