Add Terms & Conditions agreement checkbox to user signup #4

Closed
opened 2026-01-03 17:40:47 +00:00 by egullickson · 1 comment
Owner

Description

Add a required Terms & Conditions checkbox to the signup form with comprehensive legal audit trail.

Requirements

Frontend

  • Checkbox with "I agree to the [Terms & Conditions]" label positioned above submit button
  • "Terms & Conditions" is a clickable link that opens PDF in new tab
  • Required field - blocks form submission if unchecked
  • Inline validation error when unchecked: "You must agree to the Terms & Conditions to create an account"
  • Must work on both mobile and desktop viewports
  • Checkbox must be keyboard navigable with proper ARIA labels

Backend/Database

Store comprehensive legal audit record with the following fields:

  • user_id: Reference to the created user
  • agreed_at: Timestamp in UTC
  • ip_address: Client IP address (check X-Forwarded-For for proxy scenarios)
  • user_agent: Browser/client user agent string
  • terms_version: Extract from filename (e.g., "v2026-01-03")
  • terms_url: Full URL path to the PDF (e.g., "/docs/v2026-01-03.pdf")
  • terms_content_hash: SHA-256 hash of the PDF file contents

Acceptance Criteria

Frontend:

  • Checkbox appears on signup form above submit button
  • Checkbox styling matches existing form elements
  • "Terms & Conditions" link opens PDF in new tab
  • Form submission blocked if checkbox unchecked
  • Inline error displays when validation fails
  • Works correctly on desktop viewports
  • Works correctly on mobile viewports
  • Keyboard navigation functions properly

Backend/Database:

  • Migration creates terms_agreements table with all required columns
  • Registration endpoint captures IP address from request headers
  • Registration endpoint captures User-Agent header
  • Timestamp stored in UTC
  • SHA-256 hash of PDF computed and stored
  • Terms version extracted from filename and stored
  • Terms URL stored as relative path
  • Agreement record created atomically with user creation

Files

  • PDF location: frontend/public/docs/v2026-01-03.pdf
  • Plan file: .claude/plans/cryptic-swimming-clock.md

Scope

  • In scope: Initial signup T&C agreement only
  • Out of scope: Re-consent flow for existing users when T&C changes (future enhancement)
## Description Add a required Terms & Conditions checkbox to the signup form with comprehensive legal audit trail. ## Requirements ### Frontend - Checkbox with "I agree to the [Terms & Conditions]" label positioned **above submit button** - "Terms & Conditions" is a clickable link that opens PDF in new tab - Required field - blocks form submission if unchecked - Inline validation error when unchecked: "You must agree to the Terms & Conditions to create an account" - Must work on both mobile and desktop viewports - Checkbox must be keyboard navigable with proper ARIA labels ### Backend/Database Store comprehensive legal audit record with the following fields: - `user_id`: Reference to the created user - `agreed_at`: Timestamp in **UTC** - `ip_address`: Client IP address (check X-Forwarded-For for proxy scenarios) - `user_agent`: Browser/client user agent string - `terms_version`: Extract from filename (e.g., "v2026-01-03") - `terms_url`: Full URL path to the PDF (e.g., "/docs/v2026-01-03.pdf") - `terms_content_hash`: SHA-256 hash of the PDF file contents ## Acceptance Criteria **Frontend:** - [ ] Checkbox appears on signup form above submit button - [ ] Checkbox styling matches existing form elements - [ ] "Terms & Conditions" link opens PDF in new tab - [ ] Form submission blocked if checkbox unchecked - [ ] Inline error displays when validation fails - [ ] Works correctly on desktop viewports - [ ] Works correctly on mobile viewports - [ ] Keyboard navigation functions properly **Backend/Database:** - [ ] Migration creates `terms_agreements` table with all required columns - [ ] Registration endpoint captures IP address from request headers - [ ] Registration endpoint captures User-Agent header - [ ] Timestamp stored in UTC - [ ] SHA-256 hash of PDF computed and stored - [ ] Terms version extracted from filename and stored - [ ] Terms URL stored as relative path - [ ] Agreement record created atomically with user creation ## Files - PDF location: `frontend/public/docs/v2026-01-03.pdf` - Plan file: `.claude/plans/cryptic-swimming-clock.md` ## Scope - **In scope**: Initial signup T&C agreement only - **Out of scope**: Re-consent flow for existing users when T&C changes (future enhancement)
egullickson added the
status
backlog
type
feature
labels 2026-01-03 17:41:00 +00:00
egullickson added this to the Sprint 2026-01-05 milestone 2026-01-03 17:41:03 +00:00
egullickson added
status
ready
and removed
status
backlog
labels 2026-01-03 17:41:53 +00:00
egullickson added
status
in-progress
and removed
status
ready
labels 2026-01-03 18:05:32 +00:00
Author
Owner

Plan: Terms & Conditions Checkbox

Phase: Planning | Agent: Planner | Status: AWAITING_REVIEW

Overview

Add a required Terms & Conditions checkbox to the signup form with comprehensive legal audit trail. Creates a new terms-agreement feature capsule following the project's established architecture pattern. The checkbox blocks form submission until accepted, links to the existing PDF, and records all required audit fields (IP, user agent, timestamp, content hash) atomically with user creation.

Planning Context

Decision Log

Decision Reasoning Chain
New terms-agreement feature capsule doc-derived CLAUDE.md pattern -> feature capsules isolate concerns -> re-consent flows would require extraction from user-profile later -> separate capsule enables future extension without refactoring
Transaction for atomic creation Legal requirement for audit integrity -> if profile creates but terms fails, user exists without consent record -> transaction ensures both succeed or both fail
PDF hash computed at startup Hash is static (PDF doesn't change per request) -> computing per-request wastes CPU -> startup computation amortizes cost across all signups
IP from X-Forwarded-For first Traefik reverse proxy sits in front of backend -> request.ip shows Traefik IP, not client -> X-Forwarded-For contains actual client IP
Zod literal(true) for checkbox Must be checked to submit -> literal(true) fails validation if false/undefined -> provides clear error message on validation failure

Rejected Alternatives

Alternative Why Rejected
Add to user-profile feature Couples legal compliance with profile management; re-consent flows would require extraction; violates single-responsibility
Add to auth feature Auth handles authentication (tokens, sessions), not legal compliance; would overload auth with unrelated concerns
MUI Checkbox component SignupForm uses custom HTML inputs with Tailwind for consistent dark theme styling; MUI would break visual consistency
Hash PDF per request PDF is static; per-request hashing wastes CPU cycles on identical computation

Constraints & Assumptions

  • PDF exists at frontend/public/docs/v2026-01-03.pdf (verified, 13573 bytes)
  • Mobile + desktop required (CLAUDE.md: 320px, 768px, 1920px viewports)
  • Touch targets >= 44px (CLAUDE.md accessibility requirement)
  • Database: snake_case, TypeScript: camelCase (CLAUDE.md naming conventions)
  • Integration tests preferred (CLAUDE.md: CI/CD runs integration tests)

Known Risks

Risk Mitigation Anchor
X-Forwarded-For spoofing Traefik is trusted proxy; header reflects actual client IP in production Traefik config (trusted)
PDF hash mismatch after update Version in filename (v2026-01-03) allows tracking; future PDF updates require new version Manual process

Invisible Knowledge

Architecture

+------------------+     +------------------+
|   SignupForm     |     |   SignupPage     |
|  (checkbox UI)   | --> |  (submit handler)|
+------------------+     +------------------+
                                |
                                v
                    +-----------------------+
                    |   authApi.signup()    |
                    | + termsAccepted field |
                    +-----------------------+
                                |
                                v
+------------------+     +------------------+
|  AuthController  |     |   AuthService    |
| (extract IP, UA) | --> |   signup()       |
+------------------+     +------------------+
                                |
                    +-----------+-----------+
                    |                       |
                    v                       v
        +------------------+     +---------------------+
        | UserProfileRepo  |     | TermsAgreementRepo  |
        |    create()      |     |     create()        |
        +------------------+     +---------------------+
                    |                       |
                    v                       v
        +------------------+     +---------------------+
        | user_profiles    |     | terms_agreements    |
        +------------------+     +---------------------+

Data Flow

  1. User checks checkbox, clicks Create Account
  2. Frontend sends {email, password, termsAccepted: true}
  3. Controller extracts IP (X-Forwarded-For || request.ip), User-Agent
  4. Service creates Auth0 user
  5. In transaction: creates user_profile + terms_agreement
  6. Returns success with userId

Invariants

  • Every user in user_profiles has exactly one record in terms_agreements
  • terms_agreements.agreed_at is always UTC
  • terms_content_hash matches SHA-256 of PDF at signup time

Milestones

Milestone 1: Database Layer

Files: backend/src/features/terms-agreement/migrations/001_create_terms_agreements.sql

Requirements:

  • Create terms_agreements table with columns: id (UUID), user_id (VARCHAR, FK concept), agreed_at (TIMESTAMPTZ), ip_address (VARCHAR), user_agent (TEXT), terms_version (VARCHAR), terms_url (VARCHAR), terms_content_hash (VARCHAR)
  • Index on user_id for lookup
  • updated_at trigger

Acceptance Criteria:

  • Migration runs without error
  • Table exists with all columns
  • Index exists on user_id

Code Changes:

-- Terms Agreements Table
-- Stores legal audit trail for Terms & Conditions acceptance at signup

CREATE TABLE IF NOT EXISTS terms_agreements (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  user_id VARCHAR(255) NOT NULL,
  agreed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
  ip_address VARCHAR(45) NOT NULL,
  user_agent TEXT NOT NULL,
  terms_version VARCHAR(50) NOT NULL,
  terms_url VARCHAR(255) NOT NULL,
  terms_content_hash VARCHAR(64) NOT NULL,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- Index for user lookup
CREATE INDEX IF NOT EXISTS idx_terms_agreements_user_id ON terms_agreements(user_id);

-- Trigger for updated_at
CREATE OR REPLACE FUNCTION update_terms_agreements_updated_at()
RETURNS TRIGGER AS $$
BEGIN
  NEW.updated_at = CURRENT_TIMESTAMP;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER terms_agreements_updated_at
  BEFORE UPDATE ON terms_agreements
  FOR EACH ROW
  EXECUTE FUNCTION update_terms_agreements_updated_at();

Milestone 2: Backend Feature Capsule

Files:

  • backend/src/features/terms-agreement/data/terms-agreement.repository.ts
  • backend/src/features/terms-agreement/domain/terms-agreement.types.ts
  • backend/src/features/terms-agreement/index.ts
  • backend/src/features/auth/domain/auth.service.ts (modify)
  • backend/src/features/auth/api/auth.validation.ts (modify)
  • backend/src/features/auth/api/auth.controller.ts (modify)
  • backend/src/_system/migrations/run-all.ts (modify)

Requirements:

  • Create TermsAgreementRepository with create() method
  • Implement mapRow() for snake_case to camelCase
  • Add termsAccepted to signup validation schema
  • Modify AuthService.signup() to accept terms data and create agreement in transaction
  • Extract IP and User-Agent in controller
  • Register terms-agreement in migration runner

Acceptance Criteria:

  • Repository creates terms_agreement record
  • Signup fails if termsAccepted is missing/false
  • IP and User-Agent captured from request
  • User profile and terms agreement created atomically

Tests:

  • Test files: backend/src/features/terms-agreement/tests/integration/terms-agreement.integration.test.ts
  • Test type: integration
  • Backing: doc-derived (CLAUDE.md CI/CD)
  • Scenarios:
    • Normal: signup with termsAccepted=true creates both records
    • Error: signup with termsAccepted=false returns 400
    • Error: transaction rollback if terms_agreement insert fails

Milestone 3: Frontend Integration

Files:

  • frontend/src/features/auth/components/SignupForm.tsx (modify)
  • frontend/src/features/auth/types/auth.types.ts (modify)
  • frontend/src/features/auth/api/auth.api.ts (modify)

Requirements:

  • Add termsAccepted boolean to SignupRequest type
  • Add checkbox with link above submit button in SignupForm
  • Add termsAccepted: z.literal(true) to Zod schema
  • Display inline error when checkbox unchecked
  • Link opens PDF in new tab
  • 44px touch target for checkbox area

Acceptance Criteria:

  • Checkbox visible above submit button
  • "Terms & Conditions" text is clickable link opening PDF
  • Form blocked if checkbox unchecked
  • Error message displays: "You must agree to the Terms & Conditions to create an account"
  • Works on mobile (320px) and desktop (1920px)
  • Keyboard navigation functional

Code Changes (SignupForm.tsx, insert before submit button div):

--- a/frontend/src/features/auth/components/SignupForm.tsx
+++ b/frontend/src/features/auth/components/SignupForm.tsx
@@ -137,6 +137,28 @@ export const SignupForm: React.FC<SignupFormProps> = ({ onSubmit, loading }) =>
         )}
       </div>

+      <div className="flex items-start min-h-[44px]">
+        <label className="flex items-start cursor-pointer">
+          <input
+            {...register('termsAccepted')}
+            type="checkbox"
+            className="w-5 h-5 mt-0.5 rounded border-silverstone text-primary-600 focus:ring-abudhabi dark:border-silverstone dark:focus:ring-abudhabi"
+            aria-label="I agree to the Terms and Conditions"
+          />
+          <span className="ml-2 text-sm text-avus">
+            I agree to the{' '}
+            <a
+              href="/docs/v2026-01-03.pdf"
+              target="_blank"
+              rel="noopener noreferrer"
+              className="text-abudhabi hover:underline"
+            >
+              Terms & Conditions
+            </a>
+          </span>
+        </label>
+      </div>
+      {errors.termsAccepted && (
+        <p className="text-sm text-red-400">{errors.termsAccepted.message}</p>
+      )}
+
       <div className="pt-4">
         <Button type="submit" loading={loading} className="w-full min-h-[44px]">

Milestone 4: Documentation

Files:

  • backend/src/features/terms-agreement/README.md
  • backend/src/features/CLAUDE.md (update index)

Requirements:

  • Create README.md for terms-agreement feature
  • Update features CLAUDE.md index with terms-agreement entry

Acceptance Criteria:

  • README.md documents feature purpose, audit fields, and data flow
  • CLAUDE.md index includes terms-agreement directory

Milestone Dependencies

M1 (Database) --> M2 (Backend) --> M3 (Frontend) --> M4 (Docs)

Sequential dependencies - each milestone depends on the previous.


Verdict: AWAITING_REVIEW | Next: Quality review of plan

## Plan: Terms & Conditions Checkbox **Phase**: Planning | **Agent**: Planner | **Status**: AWAITING_REVIEW ## Overview Add a required Terms & Conditions checkbox to the signup form with comprehensive legal audit trail. Creates a new `terms-agreement` feature capsule following the project's established architecture pattern. The checkbox blocks form submission until accepted, links to the existing PDF, and records all required audit fields (IP, user agent, timestamp, content hash) atomically with user creation. ## Planning Context ### Decision Log | Decision | Reasoning Chain | |----------|-----------------| | New terms-agreement feature capsule | doc-derived CLAUDE.md pattern -> feature capsules isolate concerns -> re-consent flows would require extraction from user-profile later -> separate capsule enables future extension without refactoring | | Transaction for atomic creation | Legal requirement for audit integrity -> if profile creates but terms fails, user exists without consent record -> transaction ensures both succeed or both fail | | PDF hash computed at startup | Hash is static (PDF doesn't change per request) -> computing per-request wastes CPU -> startup computation amortizes cost across all signups | | IP from X-Forwarded-For first | Traefik reverse proxy sits in front of backend -> request.ip shows Traefik IP, not client -> X-Forwarded-For contains actual client IP | | Zod literal(true) for checkbox | Must be checked to submit -> literal(true) fails validation if false/undefined -> provides clear error message on validation failure | ### Rejected Alternatives | Alternative | Why Rejected | |-------------|--------------| | Add to user-profile feature | Couples legal compliance with profile management; re-consent flows would require extraction; violates single-responsibility | | Add to auth feature | Auth handles authentication (tokens, sessions), not legal compliance; would overload auth with unrelated concerns | | MUI Checkbox component | SignupForm uses custom HTML inputs with Tailwind for consistent dark theme styling; MUI would break visual consistency | | Hash PDF per request | PDF is static; per-request hashing wastes CPU cycles on identical computation | ### Constraints & Assumptions - PDF exists at `frontend/public/docs/v2026-01-03.pdf` (verified, 13573 bytes) - Mobile + desktop required (CLAUDE.md: 320px, 768px, 1920px viewports) - Touch targets >= 44px (CLAUDE.md accessibility requirement) - Database: snake_case, TypeScript: camelCase (CLAUDE.md naming conventions) - Integration tests preferred (CLAUDE.md: CI/CD runs integration tests) ### Known Risks | Risk | Mitigation | Anchor | |------|------------|--------| | X-Forwarded-For spoofing | Traefik is trusted proxy; header reflects actual client IP in production | Traefik config (trusted) | | PDF hash mismatch after update | Version in filename (v2026-01-03) allows tracking; future PDF updates require new version | Manual process | ## Invisible Knowledge ### Architecture ``` +------------------+ +------------------+ | SignupForm | | SignupPage | | (checkbox UI) | --> | (submit handler)| +------------------+ +------------------+ | v +-----------------------+ | authApi.signup() | | + termsAccepted field | +-----------------------+ | v +------------------+ +------------------+ | AuthController | | AuthService | | (extract IP, UA) | --> | signup() | +------------------+ +------------------+ | +-----------+-----------+ | | v v +------------------+ +---------------------+ | UserProfileRepo | | TermsAgreementRepo | | create() | | create() | +------------------+ +---------------------+ | | v v +------------------+ +---------------------+ | user_profiles | | terms_agreements | +------------------+ +---------------------+ ``` ### Data Flow 1. User checks checkbox, clicks Create Account 2. Frontend sends `{email, password, termsAccepted: true}` 3. Controller extracts IP (`X-Forwarded-For || request.ip`), User-Agent 4. Service creates Auth0 user 5. In transaction: creates user_profile + terms_agreement 6. Returns success with userId ### Invariants - Every user in `user_profiles` has exactly one record in `terms_agreements` - `terms_agreements.agreed_at` is always UTC - `terms_content_hash` matches SHA-256 of PDF at signup time ## Milestones ### Milestone 1: Database Layer **Files**: `backend/src/features/terms-agreement/migrations/001_create_terms_agreements.sql` **Requirements**: - Create `terms_agreements` table with columns: id (UUID), user_id (VARCHAR, FK concept), agreed_at (TIMESTAMPTZ), ip_address (VARCHAR), user_agent (TEXT), terms_version (VARCHAR), terms_url (VARCHAR), terms_content_hash (VARCHAR) - Index on user_id for lookup - updated_at trigger **Acceptance Criteria**: - Migration runs without error - Table exists with all columns - Index exists on user_id **Code Changes**: ```sql -- Terms Agreements Table -- Stores legal audit trail for Terms & Conditions acceptance at signup CREATE TABLE IF NOT EXISTS terms_agreements ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id VARCHAR(255) NOT NULL, agreed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, ip_address VARCHAR(45) NOT NULL, user_agent TEXT NOT NULL, terms_version VARCHAR(50) NOT NULL, terms_url VARCHAR(255) NOT NULL, terms_content_hash VARCHAR(64) NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); -- Index for user lookup CREATE INDEX IF NOT EXISTS idx_terms_agreements_user_id ON terms_agreements(user_id); -- Trigger for updated_at CREATE OR REPLACE FUNCTION update_terms_agreements_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = CURRENT_TIMESTAMP; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER terms_agreements_updated_at BEFORE UPDATE ON terms_agreements FOR EACH ROW EXECUTE FUNCTION update_terms_agreements_updated_at(); ``` --- ### Milestone 2: Backend Feature Capsule **Files**: - `backend/src/features/terms-agreement/data/terms-agreement.repository.ts` - `backend/src/features/terms-agreement/domain/terms-agreement.types.ts` - `backend/src/features/terms-agreement/index.ts` - `backend/src/features/auth/domain/auth.service.ts` (modify) - `backend/src/features/auth/api/auth.validation.ts` (modify) - `backend/src/features/auth/api/auth.controller.ts` (modify) - `backend/src/_system/migrations/run-all.ts` (modify) **Requirements**: - Create TermsAgreementRepository with create() method - Implement mapRow() for snake_case to camelCase - Add termsAccepted to signup validation schema - Modify AuthService.signup() to accept terms data and create agreement in transaction - Extract IP and User-Agent in controller - Register terms-agreement in migration runner **Acceptance Criteria**: - Repository creates terms_agreement record - Signup fails if termsAccepted is missing/false - IP and User-Agent captured from request - User profile and terms agreement created atomically **Tests**: - **Test files**: `backend/src/features/terms-agreement/tests/integration/terms-agreement.integration.test.ts` - **Test type**: integration - **Backing**: doc-derived (CLAUDE.md CI/CD) - **Scenarios**: - Normal: signup with termsAccepted=true creates both records - Error: signup with termsAccepted=false returns 400 - Error: transaction rollback if terms_agreement insert fails --- ### Milestone 3: Frontend Integration **Files**: - `frontend/src/features/auth/components/SignupForm.tsx` (modify) - `frontend/src/features/auth/types/auth.types.ts` (modify) - `frontend/src/features/auth/api/auth.api.ts` (modify) **Requirements**: - Add termsAccepted boolean to SignupRequest type - Add checkbox with link above submit button in SignupForm - Add termsAccepted: z.literal(true) to Zod schema - Display inline error when checkbox unchecked - Link opens PDF in new tab - 44px touch target for checkbox area **Acceptance Criteria**: - Checkbox visible above submit button - "Terms & Conditions" text is clickable link opening PDF - Form blocked if checkbox unchecked - Error message displays: "You must agree to the Terms & Conditions to create an account" - Works on mobile (320px) and desktop (1920px) - Keyboard navigation functional **Code Changes** (SignupForm.tsx, insert before submit button div): ```diff --- a/frontend/src/features/auth/components/SignupForm.tsx +++ b/frontend/src/features/auth/components/SignupForm.tsx @@ -137,6 +137,28 @@ export const SignupForm: React.FC<SignupFormProps> = ({ onSubmit, loading }) => )} </div> + <div className="flex items-start min-h-[44px]"> + <label className="flex items-start cursor-pointer"> + <input + {...register('termsAccepted')} + type="checkbox" + className="w-5 h-5 mt-0.5 rounded border-silverstone text-primary-600 focus:ring-abudhabi dark:border-silverstone dark:focus:ring-abudhabi" + aria-label="I agree to the Terms and Conditions" + /> + <span className="ml-2 text-sm text-avus"> + I agree to the{' '} + <a + href="/docs/v2026-01-03.pdf" + target="_blank" + rel="noopener noreferrer" + className="text-abudhabi hover:underline" + > + Terms & Conditions + </a> + </span> + </label> + </div> + {errors.termsAccepted && ( + <p className="text-sm text-red-400">{errors.termsAccepted.message}</p> + )} + <div className="pt-4"> <Button type="submit" loading={loading} className="w-full min-h-[44px]"> ``` --- ### Milestone 4: Documentation **Files**: - `backend/src/features/terms-agreement/README.md` - `backend/src/features/CLAUDE.md` (update index) **Requirements**: - Create README.md for terms-agreement feature - Update features CLAUDE.md index with terms-agreement entry **Acceptance Criteria**: - README.md documents feature purpose, audit fields, and data flow - CLAUDE.md index includes terms-agreement directory ## Milestone Dependencies ``` M1 (Database) --> M2 (Backend) --> M3 (Frontend) --> M4 (Docs) ``` Sequential dependencies - each milestone depends on the previous. --- *Verdict*: AWAITING_REVIEW | *Next*: Quality review of plan
egullickson added
status
review
and removed
status
in-progress
labels 2026-01-03 18:28:10 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: egullickson/motovaultpro#4