diff --git a/backend/config/version.py b/backend/config/version.py
index 87ab433..6343bb0 100644
--- a/backend/config/version.py
+++ b/backend/config/version.py
@@ -2,7 +2,7 @@
# file: config/version.py
import os
-BASE_VERSION = "1.0.4RC3" # update manually when releasing features
+BASE_VERSION = "1.0.4RC4" # update manually when releasing features
def get_full_version() -> str:
"""
diff --git a/frontend/vue-app/src/assets/styles.css b/frontend/vue-app/src/assets/styles.css
index 1e466bd..174ad02 100644
--- a/frontend/vue-app/src/assets/styles.css
+++ b/frontend/vue-app/src/assets/styles.css
@@ -85,6 +85,12 @@
pointer-events: none;
color: var(--btn-primary);
}
+@media (max-width: 520px) {
+ .btn-link {
+ margin-top: 1rem;
+ margin-bottom: 1rem;
+ }
+}
/* Rounded button */
.round-btn {
diff --git a/frontend/vue-app/src/components/OverrideEditModal.vue b/frontend/vue-app/src/components/OverrideEditModal.vue
deleted file mode 100644
index e925375..0000000
--- a/frontend/vue-app/src/components/OverrideEditModal.vue
+++ /dev/null
@@ -1,205 +0,0 @@
-
-
-
-
Edit {{ entityName }}
-
-
-
-
{{ errorMessage }}
-
Default: {{ defaultValue }}
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/frontend/vue-app/src/components/__tests__/OverrideEditModal.spec.ts b/frontend/vue-app/src/components/__tests__/OverrideEditModal.spec.ts
deleted file mode 100644
index bdaa3b6..0000000
--- a/frontend/vue-app/src/components/__tests__/OverrideEditModal.spec.ts
+++ /dev/null
@@ -1,208 +0,0 @@
-import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
-import { mount, VueWrapper } from '@vue/test-utils'
-import { nextTick } from 'vue'
-import OverrideEditModal from '../OverrideEditModal.vue'
-
-// Mock API functions
-vi.mock('@/common/api', () => ({
- setChildOverride: vi.fn(),
- parseErrorResponse: vi.fn(() => ({ msg: 'Test error', code: 'TEST_ERROR' })),
-}))
-
-import { setChildOverride } from '@/common/api'
-
-global.alert = vi.fn()
-
-describe('OverrideEditModal', () => {
- let wrapper: VueWrapper
-
- const defaultProps = {
- isOpen: true,
- childId: 'child-123',
- entityId: 'task-456',
- entityType: 'task' as 'task' | 'reward',
- entityName: 'Test Task',
- defaultValue: 100,
- currentOverride: undefined,
- }
-
- beforeEach(() => {
- vi.clearAllMocks()
- })
-
- afterEach(() => {
- if (wrapper) {
- wrapper.unmount()
- }
- })
-
- describe('Modal Display', () => {
- it('renders when isOpen is true', () => {
- wrapper = mount(OverrideEditModal, { props: defaultProps })
- expect(wrapper.find('.modal-backdrop').exists()).toBe(true)
- expect(wrapper.text()).toContain('Test Task')
- })
-
- it('does not render when isOpen is false', () => {
- wrapper = mount(OverrideEditModal, { props: { ...defaultProps, isOpen: false } })
- expect(wrapper.find('.modal-backdrop').exists()).toBe(false)
- })
-
- it('displays entity information correctly for tasks', () => {
- wrapper = mount(OverrideEditModal, { props: defaultProps })
- expect(wrapper.text()).toContain('Test Task')
- expect(wrapper.text()).toContain('New Points')
- })
-
- it('displays entity information correctly for rewards', () => {
- wrapper = mount(OverrideEditModal, {
- props: { ...defaultProps, entityType: 'reward', entityName: 'Test Reward' },
- })
- expect(wrapper.text()).toContain('Test Reward')
- expect(wrapper.text()).toContain('New Cost')
- })
- })
-
- describe('Input Validation', () => {
- it('initializes with default value when no override exists', async () => {
- wrapper = mount(OverrideEditModal, { props: defaultProps })
- await nextTick()
- const input = wrapper.find('input[type="number"]')
- expect((input.element as HTMLInputElement).value).toBe('100')
- })
-
- it('initializes with current override value when it exists', async () => {
- wrapper = mount(OverrideEditModal, {
- props: { ...defaultProps, currentOverride: 150 },
- })
- await nextTick()
- const input = wrapper.find('input[type="number"]')
- expect((input.element as HTMLInputElement).value).toBe('150')
- })
-
- it('validates input within range (0-10000)', async () => {
- wrapper = mount(OverrideEditModal, { props: defaultProps })
- const input = wrapper.find('input[type="number"]')
- const saveButton = wrapper.findAll('button').find((btn) => btn.text() === 'Save')
-
- // Valid value
- await input.setValue(5000)
- await nextTick()
- expect(saveButton?.attributes('disabled')).toBeUndefined()
-
- // Zero is valid
- await input.setValue(0)
- await nextTick()
- expect(saveButton?.attributes('disabled')).toBeUndefined()
-
- // Max is valid
- await input.setValue(10000)
- await nextTick()
- expect(saveButton?.attributes('disabled')).toBeUndefined()
- })
-
- it('shows error for values outside range', async () => {
- wrapper = mount(OverrideEditModal, { props: defaultProps })
- const input = wrapper.find('input[type="number"]')
-
- // Above max
- await input.setValue(10001)
- await nextTick()
- expect(wrapper.text()).toContain('Value must be between 0 and 10000')
- const saveButton = wrapper.findAll('button').find((btn) => btn.text() === 'Save')
- expect(saveButton?.attributes('disabled')).toBeDefined()
- })
- })
-
- describe('User Interactions', () => {
- it('emits close event when Cancel is clicked', async () => {
- wrapper = mount(OverrideEditModal, { props: defaultProps })
- const cancelButton = wrapper.findAll('button').find((btn) => btn.text() === 'Cancel')
- await cancelButton?.trigger('click')
- expect(wrapper.emitted('close')).toBeTruthy()
- })
-
- it('emits close event when clicking backdrop', async () => {
- wrapper = mount(OverrideEditModal, { props: defaultProps })
- await wrapper.find('.modal-backdrop').trigger('click')
- expect(wrapper.emitted('close')).toBeTruthy()
- })
-
- it('does not close when clicking modal dialog', async () => {
- wrapper = mount(OverrideEditModal, { props: defaultProps })
- await wrapper.find('.modal-dialog').trigger('click')
- expect(wrapper.emitted('close')).toBeFalsy()
- })
-
- it('calls API and emits events on successful save', async () => {
- ;(setChildOverride as any).mockResolvedValue({ ok: true })
-
- wrapper = mount(OverrideEditModal, { props: defaultProps })
- const input = wrapper.find('input[type="number"]')
- await input.setValue(250)
- await nextTick()
-
- const saveButton = wrapper.findAll('button').find((btn) => btn.text() === 'Save')
- await saveButton?.trigger('click')
- await nextTick()
-
- expect(setChildOverride).toHaveBeenCalledWith('child-123', 'task-456', 'task', 250)
- expect(wrapper.emitted('saved')).toBeTruthy()
- expect(wrapper.emitted('close')).toBeTruthy()
- })
-
- it('shows alert on API error', async () => {
- ;(setChildOverride as any).mockResolvedValue({ ok: false, status: 400 })
-
- wrapper = mount(OverrideEditModal, { props: defaultProps })
- const saveButton = wrapper.findAll('button').find((btn) => btn.text() === 'Save')
- await saveButton?.trigger('click')
- await nextTick()
-
- expect(global.alert).toHaveBeenCalledWith('Error: Test error')
- expect(wrapper.emitted('saved')).toBeFalsy()
- })
-
- it('does not save when validation fails', async () => {
- wrapper = mount(OverrideEditModal, { props: defaultProps })
- const input = wrapper.find('input[type="number"]')
- await input.setValue(20000)
- await nextTick()
-
- const saveButton = wrapper.findAll('button').find((btn) => btn.text() === 'Save')
- await saveButton?.trigger('click')
- await nextTick()
-
- expect(setChildOverride).not.toHaveBeenCalled()
- })
- })
-
- describe('Modal State Updates', () => {
- it('reinitializes value when modal reopens', async () => {
- wrapper = mount(OverrideEditModal, { props: { ...defaultProps, isOpen: false } })
- await nextTick()
-
- await wrapper.setProps({ isOpen: true })
- await nextTick()
-
- const input = wrapper.find('input[type="number"]')
- expect((input.element as HTMLInputElement).value).toBe('100')
- })
-
- it('uses updated currentOverride when modal reopens', async () => {
- wrapper = mount(OverrideEditModal, {
- props: { ...defaultProps, isOpen: true, currentOverride: 200 },
- })
- await nextTick()
-
- await wrapper.setProps({ isOpen: false })
- await nextTick()
-
- await wrapper.setProps({ isOpen: true, currentOverride: 300 })
- await nextTick()
-
- const input = wrapper.find('input[type="number"]')
- expect((input.element as HTMLInputElement).value).toBe('300')
- })
- })
-})
diff --git a/frontend/vue-app/src/components/auth/ParentPinSetup.vue b/frontend/vue-app/src/components/auth/ParentPinSetup.vue
index 9d9df99..5bc13c2 100644
--- a/frontend/vue-app/src/components/auth/ParentPinSetup.vue
+++ b/frontend/vue-app/src/components/auth/ParentPinSetup.vue
@@ -18,7 +18,13 @@
We've sent a 6-digit code to your email. Enter it below to continue. The code is valid for
10 minutes.
-
+
+
+
+
+
+
+
+
+ This reward is pending.
Would you like to cancel the request?
+
+
+
+
+
+
diff --git a/frontend/vue-app/src/components/child/ParentView.vue b/frontend/vue-app/src/components/child/ParentView.vue
index 8922add..5ad49fd 100644
--- a/frontend/vue-app/src/components/child/ParentView.vue
+++ b/frontend/vue-app/src/components/child/ParentView.vue
@@ -1,5 +1,5 @@