feat: enhance child edit and view components with improved form handling and validation
All checks were successful
Chore App Build and Push Docker Images / build-and-push (push) Successful in 1m4s

- Added `requireDirty` prop to `EntityEditForm` for dirty state management.
- Updated `ChildEditView` to handle initial data loading and image selection more robustly.
- Refactored `ChildView` to remove unused reward dialog logic and prevent API calls in child mode.
- Improved type definitions for form fields and initial data in `ChildEditView`.
- Enhanced error handling in form submissions across components.
- Implemented cross-tab logout synchronization on password reset in the auth store.
- Added tests for login and entity edit form functionalities to ensure proper behavior.
- Introduced global fetch interceptor for handling unauthorized responses.
- Documented password reset flow and its implications on session management.
This commit is contained in:
2026-02-17 17:18:03 -05:00
parent 5e22e5e0ee
commit 31ea76f013
29 changed files with 1000 additions and 164 deletions

View File

@@ -162,6 +162,7 @@ def login():
payload = {
'email': norm_email,
'user_id': user.id,
'token_version': user.token_version,
'exp': datetime.utcnow() + timedelta(hours=24*7)
}
token = jwt.encode(payload, current_app.config['SECRET_KEY'], algorithm='HS256')
@@ -179,10 +180,13 @@ def me():
try:
payload = jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=['HS256'])
user_id = payload.get('user_id', '')
token_version = payload.get('token_version', 0)
user_dict = users_db.get(UserQuery.id == user_id)
user = User.from_dict(user_dict) if user_dict else None
if not user:
return jsonify({'error': 'User not found', 'code': USER_NOT_FOUND}), 404
if token_version != user.token_version:
return jsonify({'error': 'Invalid token', 'code': INVALID_TOKEN}), 401
if user.marked_for_deletion:
return jsonify({'error': 'Account marked for deletion', 'code': ACCOUNT_MARKED_FOR_DELETION}), 403
return jsonify({
@@ -268,9 +272,12 @@ def reset_password():
user.password = generate_password_hash(new_password)
user.reset_token = None
user.reset_token_created = None
user.token_version += 1
users_db.update(user.to_dict(), UserQuery.email == user.email)
return jsonify({'message': 'Password has been reset'}), 200
resp = jsonify({'message': 'Password has been reset'})
resp.set_cookie('token', '', expires=0, httponly=True, secure=True, samesite='Strict')
return resp, 200
@auth_api.route('/logout', methods=['POST'])
def logout():

View File

@@ -64,10 +64,24 @@ def list_tasks():
continue # Skip default if user version exists
filtered_tasks.append(t)
# Sort: user-created items first (by name), then default items (by name)
user_created = sorted([t for t in filtered_tasks if t.get('user_id') == user_id], key=lambda x: x['name'].lower())
default_items = sorted([t for t in filtered_tasks if t.get('user_id') is None], key=lambda x: x['name'].lower())
sorted_tasks = user_created + default_items
# Sort order:
# 1) good tasks first, then not-good tasks
# 2) within each group: user-created items first (by name), then default items (by name)
good_tasks = [t for t in filtered_tasks if t.get('is_good') is True]
not_good_tasks = [t for t in filtered_tasks if t.get('is_good') is not True]
def sort_user_then_default(tasks_group):
user_created = sorted(
[t for t in tasks_group if t.get('user_id') == user_id],
key=lambda x: x['name'].lower(),
)
default_items = sorted(
[t for t in tasks_group if t.get('user_id') is None],
key=lambda x: x['name'].lower(),
)
return user_created + default_items
sorted_tasks = sort_user_then_default(good_tasks) + sort_user_then_default(not_good_tasks)
return jsonify({'tasks': sorted_tasks}), 200

View File

@@ -29,6 +29,12 @@ def get_current_user_id():
user_id = payload.get('user_id')
if not user_id:
return None
token_version = payload.get('token_version', 0)
user = users_db.get(Query().id == user_id)
if not user:
return None
if token_version != user.get('token_version', 0):
return None
return user_id
except jwt.InvalidTokenError:
return None