Repository: openreachtech/dydx-trading-competition-frontend
Pull request #308: Add the ability to pass initial value when editing competition basic details
Author: login: hecticme
Created: 2025-04-28T11:09:53Z | Merged: 2025-04-28T12:42:37Z

# Why

* Close https://chiho-internal.openreach.tech/tasks/2707

# How

* Allow to set initial value of input fields through props when editing competition basic details.
* This is required for the competition edit feature.

# Screenshots

```vue
<AddCompetitionFormStepDetails
  :initial-form-value-hash="{
    title: 'blue',
    description: 'This is the default description',
  }"
/>
```

![image](https://github.com/user-attachments/assets/c956077b-a29a-4367-8719-6c70e2610258)




Diff:
diff --git a/app/vue/contexts/competition/AddCompetitionFormStepDetailsContext.js b/app/vue/contexts/competition/AddCompetitionFormStepDetailsContext.js
index 5a58eaa4..96a8dae3 100644
--- a/app/vue/contexts/competition/AddCompetitionFormStepDetailsContext.js
+++ b/app/vue/contexts/competition/AddCompetitionFormStepDetailsContext.js
@@ -2,6 +2,8 @@ import {
   BaseFuroContext,
 } from '@openreachtech/furo-nuxt'
 
+const FALLBACK_COMPETITION_IMAGE_URL = '/img/badges/league-badge-placeholder.png'
+
 /**
  * AddCompetitionFormStepDetailsContext
  *
@@ -75,6 +77,74 @@ export default class AddCompetitionFormStepDetailsContext extends BaseFuroContex
     return this.props.validationMessage
   }
 
+  /**
+   * get: initialFormValueHash
+   *
+   * @returns {PropsType['initialFormValueHash']}
+   */
+  get initialFormValueHash () {
+    return this.props.initialFormValueHash
+  }
+
+  /**
+   * get: initialTitle
+   *
+   * @returns {string | null}
+   */
+  get initialTitle () {
+    return this.initialFormValueHash
+      ?.title
+      ?? null
+  }
+
+  /**
+   * get: initialDescription
+   *
+   * @returns {string | null}
+   */
+  get initialDescription () {
+    return this.initialFormValueHash
+      ?.description
+      ?? null
+  }
+
+  /**
+   * get: initialCompetitionImageUrl
+   *
+   * @returns {string | null}
+   */
+  get initialCompetitionImageUrl () {
+    return this.initialFormValueHash
+      ?.competitionImageUrl
+      ?? null
+  }
+
+  /**
+   * get: imageSource
+   *
+   * @returns {string | null}
+   */
+  get imageSource () {
+    return this.imageSourceRef.value
+  }
+
+  /**
+   * Generate competition image URL.
+   *
+   * @returns {string}
+   */
+  generateCompetitionImageUrl () {
+    if (this.imageSource) {
+      return this.imageSource
+    }
+
+    if (this.initialCompetitionImageUrl) {
+      return this.initialCompetitionImageUrl
+    }
+
+    return FALLBACK_COMPETITION_IMAGE_URL
+  }
+
   /**
    * get: isUploadingImage
    *
@@ -197,7 +267,7 @@ export default class AddCompetitionFormStepDetailsContext extends BaseFuroContex
 /**
  * @typedef {import('@openreachtech/furo-nuxt/lib/contexts/BaseFuroContext').BaseFuroContextParams<PropsType> & {
  *   uploadInputShallowRef: import('vue').ShallowRef<HTMLInputElement | null>
- *   imageSourceRef: import('vue').Ref<string>
+ *   imageSourceRef: import('vue').Ref<string | null>
  *   imageIdRef: import('vue').Ref<number | null>
  *   statusReactive: StatusReactive
  *   graphqlClientHash: Record<GraphqlClientHashKeys, GraphqlClient>
@@ -225,5 +295,10 @@ export default class AddCompetitionFormStepDetailsContext extends BaseFuroContex
 /**
  * @typedef {{
  *   validationMessage: furo.ValidatorHashType['message']
+ *   initialFormValueHash: {
+ *     title: string | null
+ *     description: string | null
+ *     competitionImageUrl?: string | null
+ *   }
  * }} PropsType
  */
diff --git a/components/competition-add/AddCompetitionFormStepDetails.vue b/components/competition-add/AddCompetitionFormStepDetails.vue
index 9ad5a06f..3cf627e1 100644
--- a/components/competition-add/AddCompetitionFormStepDetails.vue
+++ b/components/competition-add/AddCompetitionFormStepDetails.vue
@@ -35,6 +35,15 @@ export default defineComponent({
       type: Object,
       required: true,
     },
+    initialFormValueHash: {
+      /** @type {import('vue').PropType<import('~/app/vue/contexts/competition/AddCompetitionFormStepDetailsContext').PropsType['initialFormValueHash']>} */
+      type: [
+        Object,
+        null,
+      ],
+      required: false,
+      default: null,
+    },
   },
 
   setup (
@@ -43,8 +52,8 @@ export default defineComponent({
   ) {
     /** @type {import('vue').ShallowRef<HTMLInputElement | null>} */
     const uploadInputShallowRef = shallowRef(null)
-    /** @type {import('vue').Ref<string>} */
-    const imageSourceRef = ref('/img/badges/league-badge-placeholder.png')
+    /** @type {import('vue').Ref<string | null>} */
+    const imageSourceRef = ref(null)
     /** @type {import('vue').Ref<number | null>} */
     const imageIdRef = ref(null)
     /** @type {import('~/app/vue/contexts/competition/AddCompetitionFormStepDetailsContext').StatusReactive} */
@@ -94,6 +103,7 @@ export default defineComponent({
       </span>
 
       <AppInput name="title"
+        :value="context.initialTitle"
         placeholder="Give your league a name (max 30 characters)"
         :has-error="Boolean(context.validationMessage.title)"
         :error-message="context.validationMessage.title"
@@ -107,6 +117,7 @@ export default defineComponent({
 
       <AppTextarea name="description"
         rows="10"
+        :value="context.initialDescription"
         placeholder="Describe your league (max 250 characters)"
         :has-error="Boolean(context.validationMessage.description)"
         :error-message="context.validationMessage.description"
@@ -141,7 +152,7 @@ export default defineComponent({
           })"
         >
 
-        <img :src="context.imageSourceRef.value"
+        <img :src="context.generateCompetitionImageUrl()"
           alt="League badge"
           class="image"
         >


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR adds the ability to set initial values for competition basic details by introducing new props and context getters.  
- Added props (initialFormValueHash and initialCompetitionImageUrl) to AddCompetitionFormStepDetails.vue to enable passing in initial field values.  
- Updated the context (AddCompetitionFormStepDetailsContext.js) to include getters for initialTitle, initialDescription, and a method to generate the competition image URL.

### Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

| File | Description |
| ---- | ----------- |
| components/competition-add/AddCompetitionFormStepDetails.vue | Introduces new props and updates form inputs to use initial values from context. |
| app/vue/contexts/competition/AddCompetitionFormStepDetailsContext.js | Adds getters for initial form values and refactors image URL generation logic. |




state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T11:10:37Z
user: login: EG-easy
body: 🧉
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T11:17:05Z
user: login: StewEucen
body: 👀
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T11:20:00Z
user: login: hecticme
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-28T11:22:13Z
user: login: StewEucen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T11:24:16Z
user: login: StewEucen
body: ✍
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T11:25:46Z

Review comments:
diff_hunk: @@ -75,6 +77,72 @@ export default class AddCompetitionFormStepDetailsContext extends BaseFuroContex
     return this.props.validationMessage
   }
 
+  /**
+   * get: initialFormValueHash
+   *
+   * @returns {PropsType['initialFormValueHash']}
+   */
+  get initialFormValueHash () {
+    return this.props.initialFormValueHash
+  }
+
+  /**
+   * get: initialTitle
+   *
+   * @returns {string | null}
+   */
+  get initialTitle () {
+    return this.initialFormValueHash.title
path: app/vue/contexts/competition/AddCompetitionFormStepDetailsContext.js
user: login: Copilot
body: The getter assumes that 'initialFormValueHash' is always an object, but the prop default is null; consider using optional chaining (e.g., 'this.initialFormValueHash?.title') or a default value to avoid potential runtime errors.
```suggestion
    return this.initialFormValueHash?.title
```
created_at: 2025-04-28T11:10:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -75,6 +77,72 @@ export default class AddCompetitionFormStepDetailsContext extends BaseFuroContex
     return this.props.validationMessage
   }
 
+  /**
+   * get: initialFormValueHash
+   *
+   * @returns {PropsType['initialFormValueHash']}
+   */
+  get initialFormValueHash () {
+    return this.props.initialFormValueHash
+  }
+
+  /**
+   * get: initialTitle
+   *
+   * @returns {string | null}
+   */
+  get initialTitle () {
+    return this.initialFormValueHash.title
+  }
+
+  /**
+   * get: initialDescription
+   *
+   * @returns {string | null}
+   */
+  get initialDescription () {
+    return this.initialFormValueHash.description
path: app/vue/contexts/competition/AddCompetitionFormStepDetailsContext.js
user: login: Copilot
body: The getter assumes that 'initialFormValueHash' is not null, which could result in a runtime error; use optional chaining (e.g., 'this.initialFormValueHash?.description') or ensure a default object is provided.
```suggestion
    return this.initialFormValueHash?.description
```
created_at: 2025-04-28T11:10:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -75,6 +77,78 @@ export default class AddCompetitionFormStepDetailsContext extends BaseFuroContex
     return this.props.validationMessage
   }
 
+  /**
+   * get: initialFormValueHash
+   *
+   * @returns {PropsType['initialFormValueHash']}
+   */
+  get initialFormValueHash () {
+    return this.props.initialFormValueHash
+  }
+
+  /**
+   * get: initialTitle
+   *
+   * @returns {string | null}
+   */
+  get initialTitle () {
+    return this.initialFormValueHash
+      ?.title
+      ?? null
+  }
+
+  /**
+   * get: initialDescription
+   *
+   * @returns {string | null}
+   */
+  get initialDescription () {
+    return this.initialFormValueHash
+      ?.description
+      ?? null
+  }
+
+  /**
+   * get: initialCompetitionImageUrl
+   *
+   * @returns {string | null}
+   */
+  get initialCompetitionImageUrl () {
+    return this.initialFormValueHash
+      ?.competitionImageUrl
+      ?? null
+  }
+
+  /**
+   * get: imageSource
+   *
+   * @returns {string | null}
+   */
+  get imageSource () {
+    return this.imageSourceRef.value
+  }
+
+  /**
+   * Generate competition image URL.
+   *
+   * @returns {string}
+   */
+  generateCompetitionImageUrl () {
+    if (
+      this.initialCompetitionImageUrl === null
+      && this.imageSource === null
+    ) {
+      return FALLBACK_COMPETITION_IMAGE_URL
+    }
+
+    if (this.imageSource !== null) {
+      return this.imageSource
+    }
+
+    return this.initialCompetitionImageUrl
path: app/vue/contexts/competition/AddCompetitionFormStepDetailsContext.js
user: login: StewEucen
body: @hecticme 

Is there possibility that `#initialCompetitionImageUrl` is `undefined`?
created_at: 2025-04-28T11:19:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -75,6 +77,78 @@ export default class AddCompetitionFormStepDetailsContext extends BaseFuroContex
     return this.props.validationMessage
   }
 
+  /**
+   * get: initialFormValueHash
+   *
+   * @returns {PropsType['initialFormValueHash']}
+   */
+  get initialFormValueHash () {
+    return this.props.initialFormValueHash
+  }
+
+  /**
+   * get: initialTitle
+   *
+   * @returns {string | null}
+   */
+  get initialTitle () {
+    return this.initialFormValueHash
+      ?.title
+      ?? null
+  }
+
+  /**
+   * get: initialDescription
+   *
+   * @returns {string | null}
+   */
+  get initialDescription () {
+    return this.initialFormValueHash
+      ?.description
+      ?? null
+  }
+
+  /**
+   * get: initialCompetitionImageUrl
+   *
+   * @returns {string | null}
+   */
+  get initialCompetitionImageUrl () {
+    return this.initialFormValueHash
+      ?.competitionImageUrl
+      ?? null
+  }
+
+  /**
+   * get: imageSource
+   *
+   * @returns {string | null}
+   */
+  get imageSource () {
+    return this.imageSourceRef.value
+  }
+
+  /**
+   * Generate competition image URL.
+   *
+   * @returns {string}
+   */
+  generateCompetitionImageUrl () {
+    if (
+      this.initialCompetitionImageUrl === null
+      && this.imageSource === null
+    ) {
+      return FALLBACK_COMPETITION_IMAGE_URL
+    }
+
+    if (this.imageSource !== null) {
+      return this.imageSource
+    }
+
+    return this.initialCompetitionImageUrl
path: app/vue/contexts/competition/AddCompetitionFormStepDetailsContext.js
user: login: hecticme
body: @StewEucen 

Yes, the user can choose not to upload an image.
created_at: 2025-04-28T11:22:13Z
author_association: MEMBER
diff_hunk: @@ -75,6 +77,78 @@ export default class AddCompetitionFormStepDetailsContext extends BaseFuroContex
     return this.props.validationMessage
   }
 
+  /**
+   * get: initialFormValueHash
+   *
+   * @returns {PropsType['initialFormValueHash']}
+   */
+  get initialFormValueHash () {
+    return this.props.initialFormValueHash
+  }
+
+  /**
+   * get: initialTitle
+   *
+   * @returns {string | null}
+   */
+  get initialTitle () {
+    return this.initialFormValueHash
+      ?.title
+      ?? null
+  }
+
+  /**
+   * get: initialDescription
+   *
+   * @returns {string | null}
+   */
+  get initialDescription () {
+    return this.initialFormValueHash
+      ?.description
+      ?? null
+  }
+
+  /**
+   * get: initialCompetitionImageUrl
+   *
+   * @returns {string | null}
+   */
+  get initialCompetitionImageUrl () {
+    return this.initialFormValueHash
+      ?.competitionImageUrl
+      ?? null
+  }
+
+  /**
+   * get: imageSource
+   *
+   * @returns {string | null}
+   */
+  get imageSource () {
+    return this.imageSourceRef.value
+  }
+
+  /**
+   * Generate competition image URL.
+   *
+   * @returns {string}
+   */
+  generateCompetitionImageUrl () {
+    if (
+      this.initialCompetitionImageUrl === null
+      && this.imageSource === null
+    ) {
+      return FALLBACK_COMPETITION_IMAGE_URL
+    }
+
+    if (this.imageSource !== null) {
+      return this.imageSource
+    }
+
+    return this.initialCompetitionImageUrl
+      ?? FALLBACK_COMPETITION_IMAGE_URL
+  }
path: app/vue/contexts/competition/AddCompetitionFormStepDetailsContext.js
user: login: StewEucen
body: @hecticme 

Is it the same logic? 🤔

```suggestion
    if (this.imageSource) {
      return this.imageSource
    }
  
    if (this.initialCompetitionImageUrl) {
      return this.initialCompetitionImageUrl
    }
    
    return FALLBACK_COMPETITION_IMAGE_URL
  }
```
created_at: 2025-04-28T11:23:29Z
author_association: CONTRIBUTOR

PR comments:
user: login: cloudflare-workers-and-pages[bot]
created_at: 2025-04-28T11:13:55Z
body: ## Deploying dydx-trading-competition-frontend with &nbsp;<a href="https://pages.dev"><img alt="Cloudflare Pages" src="https://user-images.githubusercontent.com/23264/106598434-9e719e00-654f-11eb-9e59-6167043cfa01.png" width="16"></a> &nbsp;Cloudflare Pages

<table><tr><td><strong>Latest commit:</strong> </td><td>
<code>a681540</code>
</td></tr>
<tr><td><strong>Status:</strong></td><td>&nbsp;✅&nbsp; Deploy successful!</td></tr>
<tr><td><strong>Preview URL:</strong></td><td>
<a href='https://2dfcc344.dydx-trading-competition-frontend.pages.dev'>https://2dfcc344.dydx-trading-competition-frontend.pages.dev</a>
</td></tr>
<tr><td><strong>Branch Preview URL:</strong></td><td>
<a href='https://feat-initial-value-competiti.dydx-trading-competition-frontend.pages.dev'>https://feat-initial-value-competiti.dydx-trading-competition-frontend.pages.dev</a>
</td></tr>
</table>

[View logs](https://dash.cloudflare.com/82be5ca4754b67b076b29647ce0cc739/pages/view/dydx-trading-competition-frontend/2dfcc344-2868-4403-88da-43dda3bab76a)

author_association: NONE
user: login: cloudflare-workers-and-pages[bot]
created_at: 2025-04-28T11:15:35Z
body: ## Deploying dydx-trading-competition-frontend-production with &nbsp;<a href="https://pages.dev"><img alt="Cloudflare Pages" src="https://user-images.githubusercontent.com/23264/106598434-9e719e00-654f-11eb-9e59-6167043cfa01.png" width="16"></a> &nbsp;Cloudflare Pages

<table><tr><td><strong>Latest commit:</strong> </td><td>
<code>a681540</code>
</td></tr>
<tr><td><strong>Status:</strong></td><td>&nbsp;✅&nbsp; Deploy successful!</td></tr>
<tr><td><strong>Preview URL:</strong></td><td>
<a href='https://8d40bf08.dydx-trading-competition-frontend-production.pages.dev'>https://8d40bf08.dydx-trading-competition-frontend-production.pages.dev</a>
</td></tr>
<tr><td><strong>Branch Preview URL:</strong></td><td>
<a href='https://feat-initial-value-competiti.dydx-trading-competition-frontend-production.pages.dev'>https://feat-initial-value-competiti.dydx-trading-competition-frontend-production.pages.dev</a>
</td></tr>
</table>

[View logs](https://dash.cloudflare.com/82be5ca4754b67b076b29647ce0cc739/pages/view/dydx-trading-competition-frontend-production/8d40bf08-3468-4b05-9f58-26bf6b789147)

author_association: NONE