Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nijil / GRWT-5269 / Integrate product_id to product-config #70

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

nijil-deriv
Copy link
Contributor

@nijil-deriv nijil-deriv commented Mar 10, 2025

Summary by Sourcery

Integrate product_id to product-config API calls, and refactor duration selection.

Enhancements:

  • Refactor duration selection to improve user experience.
  • Update duration selection logic to handle different duration types and special cases.
  • Improve accessibility of duration selection components.
  • Refactor stake field to improve user experience.
  • Improve validation and error handling for stake input.
  • Improve accessibility of stake field component.
  • Update duration formatting to display user-friendly values.
  • Improve the logic for formatting duration values.
  • Refactor the product config API call to use product_id instead of product_type.
  • Update the product config API call to include account_uuid.

Copy link

sourcery-ai bot commented Mar 10, 2025

Reviewer's Guide by Sourcery

This pull request focuses on integrating the product_id to the product configuration API calls. It also includes several refactors and improvements to the duration and stake components, including bug fixes and test updates.

Sequence diagram for fetching product configuration

sequenceDiagram
    participant Component
    participant useProductConfig
    participant getProductConfig
    participant API

    Component->>useProductConfig: fetchProductConfig(product_id, instrument_id)
    useProductConfig->>getProductConfig: await getProductConfig(instrument_id, product_id, account_uuid)
    getProductConfig->>API: GET /v1/market/products/config?instrument_id&product_id&account_uuid
    API-->>getProductConfig: ProductConfigResponse
    getProductConfig-->>useProductConfig: ProductConfigResponse
    useProductConfig-->>Component: Update UI with config
Loading

Updated class diagram for ProductConfigRequest

classDiagram
    class ProductConfigRequest {
      -instrument_id: string
      -product_id: string
      -account_uuid: string
    }
    note for ProductConfigRequest "Added product_id and account_uuid attributes"
Loading

File-Level Changes

Change Details Files
Integrated product_id to the product configuration API calls.
  • Updated getProductConfig service to include product_id in the request parameters.
  • Modified ProductConfigRequest type to include product_id and account_uuid.
  • Updated fetchProductConfig hook to pass product_id to the getProductConfig function.
src/hooks/useProductConfig.ts
src/services/api/rest/product-config/service.ts
src/services/api/rest/product-config/types.ts
Refactored duration-related components and utilities.
  • Modified getAvailableDurationTypes function to correctly filter duration types based on API support and valid ranges.
  • Updated adaptDurationRanges function to handle different duration types and ensure at least one duration type is available.
  • Improved duration formatting and parsing logic in formatDuration, parseDuration, and formatDurationDisplay functions.
  • Updated DurationController component to use available duration types and handle value selection.
  • Fixed a bug in HoursDurationValue component related to resetting minutes after initial render.
  • Updated tests for duration-related components and utilities.
src/components/Duration/DurationController.tsx
src/components/Duration/DurationField.tsx
src/components/Duration/components/__tests__/DurationValueList.test.tsx
src/components/Duration/components/__tests__/HoursDurationValue.test.tsx
src/components/Duration/components/DurationValueList.tsx
src/adapters/duration-config-adapter.ts
src/utils/duration.ts
Refactored stake-related components and hooks.
  • Updated useStakeField hook to improve validation and error handling.
  • Improved stake increment/decrement logic in incrementStake and decrementStake functions.
  • Updated StakeField component to handle loading state and error display.
  • Updated tests for stake-related components and utilities.
src/components/Stake/StakeController.tsx
src/components/Stake/StakeField.tsx
src/components/Stake/components/StakeInput.tsx
src/components/Stake/hooks/useStakeField.ts
src/components/Stake/utils/validation.ts
src/utils/stake.ts
Updated trade store to manage product configuration and trade parameters.
  • Added productConfig, isConfigLoading, configError, and configCache properties to the trade store.
  • Added actions to set product configuration, loading state, error state, and config cache.
  • Updated setTradeType action to reset payout values when the trade type changes.
src/stores/tradeStore.ts
Miscellaneous changes.
  • Updated TradeParam component to improve accessibility and formatting.
  • Removed unused code and comments.
  • Updated dependencies and configurations.
src/components/TradeFields/TradeParam.tsx
src/config/constants.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!
  • Generate a plan of action for an issue: Comment @sourcery-ai plan on
    an issue to generate a plan of action for it.

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

OpenSSF Scorecard

PackageVersionScoreDetails

Scanned Manifest Files

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @nijil-deriv - I've reviewed your changes - here's some feedback:

Overall Comments:

  • The getProductConfig function in src/services/api/rest/product-config/service.ts now requires product_id and account_uuid - ensure all call sites are updated accordingly.
Here's what I looked at during the review
  • 🟡 General issues: 1 issue found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟡 Complexity: 2 issues found
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

}
);
return response.data;
const { instrument_id, product_id } = params;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Pass a proper account identifier instead of an empty string.

Currently, account_uuid is hardcoded as an empty string. Make sure to replace it with the actual account identifier when available to avoid unexpected API behavior.

Suggested implementation:

    params: ProductConfigRequest, accountUUID: string
): Promise<ProductConfigResponse> => {
            account_uuid: accountUUID,

Make sure to update all the callers of this function so that you pass the proper account identifier.

isInitialRender.current = true;
return () => {
isInitialRender.current = false;
export const DurationController: React.FC<DurationControllerProps> = ({ onClose }) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider extracting state management into a custom hook and splitting UI rendering into sub-components to improve readability and maintainability of the component.

Consider extracting parts of the local state and view logic into dedicated hooks or sub-components. This can reduce nested callbacks and improve readability without changing functionality. For example:

  1. Extract duration state management into a custom hook:
    Move all duration-related state and handlers into a hook (e.g., useDurationController) that returns necessary values and methods.

    // hooks/useDurationController.ts
    import { useState, useMemo } from "react";
    import { getDefaultDuration } from "@/utils/duration";
    
    export function useDurationController(initialDuration: string) {
      const [localDuration, setLocalDuration] = useState(initialDuration);
      const [value, type] = localDuration.split(" ");
      const [selectedTabType, setSelectedTabType] = useState(type);
      const [selectedValues, setSelectedValues] = useState({
        [type]: type === "hours" ? value : parseInt(value, 10),
      });
    
      const selectedValue = selectedValues[selectedTabType];
    
      const updateSelectedValue = (newVal: number | string) => {
        setSelectedValues(prev => ({ ...prev, [selectedTabType]: newVal }));
        const newDuration = `${newVal} ${selectedTabType}`;
        setLocalDuration(newDuration);
        return newDuration;
      };
    
      return {
        localDuration,
        selectedTabType,
        selectedValue,
        setSelectedTabType,
        updateSelectedValue,
        setLocalDuration,
        selectedValues,
      };
    }
  2. Split UI rendering into sub-components:
    Separate mobile and landscape views or even the tab list from the duration value list. For example:

    // components/DurationContent.tsx
    import React from "react";
    import { TabList } from "@/components/ui/tab-list";
    import { BottomSheetHeader } from "@/components/ui/bottom-sheet-header";
    import { HoursDurationValue } from "./HoursDurationValue";
    import { DurationValueList } from "./DurationValueList";
    
    export const DurationContent: React.FC<{
      isLandscape: boolean;
      availableDurationTypes: any;
      selectedTabType: string;
      selectedValue: string | number | undefined;
      onSelectTab: (type: string) => void;
      onValueSelect: (value: number | string) => void;
      onValueClick: (value: number | string) => void;
    }> = ({
      isLandscape,
      availableDurationTypes,
      selectedTabType,
      selectedValue,
      onSelectTab,
      onValueSelect,
      onValueClick,
    }) => (
      <div className={isLandscape ? "flex" : ""}>
        {!isLandscape && <BottomSheetHeader title="Duration" />}
        <TabList
          tabs={availableDurationTypes}
          selectedValue={selectedTabType}
          onSelect={onSelectTab}
          variant={isLandscape ? "vertical" : "chip"}
        />
        <div className={`flex-1 relative bg-white ${isLandscape ? "px-2" : "px-8"}`}>
          {selectedTabType === "hours" ? (
            <HoursDurationValue
              selectedValue={selectedValue?.toString() || ""}
              onValueSelect={onValueSelect}
              onValueClick={onValueClick}
            />
          ) : (
            <DurationValueList
              key={selectedTabType}
              selectedValue={selectedValue}
              durationType={selectedTabType}
              onValueSelect={onValueSelect}
              onValueClick={onValueClick}
            />
          )}
        </div>
      </div>
    );
  3. Integrate refactored hooks and sub-components into your main component:

    // DurationController.tsx
    import React, { useEffect, useRef, useMemo } from "react";
    import { DesktopTradeFieldCard } from "@/components/ui/desktop-trade-field-card";
    import { PrimaryButton } from "@/components/ui/primary-button";
    import { useTradeStore } from "@/stores/tradeStore";
    import { useBottomSheetStore } from "@/stores/bottomSheetStore";
    import { useOrientationStore } from "@/stores/orientationStore";
    import { useDebounce } from "@/hooks/useDebounce";
    import { getAvailableDurationTypes } from "@/adapters/duration-config-adapter";
    import { DurationContent } from "./components/DurationContent";
    import { useDurationController } from "./hooks/useDurationController";
    
    export const DurationController: React.FC<{ onClose?: () => void }> = ({ onClose }) => {
      const { productConfig: config, duration, setDuration } = useTradeStore();
      const { isLandscape } = useOrientationStore();
      const { setBottomSheet } = useBottomSheetStore();
      const isInitialRender = useRef(true);
    
      useEffect(() => {
        isInitialRender.current = true;
        return () => {
          isInitialRender.current = false;
        };
      }, []);
    
      const availableDurationTypes = useMemo(
        () => getAvailableDurationTypes(config, /* your DURATION_TYPES constant */),
        [config]
      );
    
      const {
        localDuration,
        selectedTabType,
        selectedValue,
        setSelectedTabType,
        updateSelectedValue,
      } = useDurationController(duration);
    
      useDebounce(localDuration, (value) => {
        if (isLandscape) setDuration(value);
      }, 300);
    
      const handleValueSelect = (value: number | string) => {
        updateSelectedValue(value);
      };
    
      const handleValueClick = (value: number | string) => {
        const newDuration = updateSelectedValue(value);
        setDuration(newDuration);
        if (isLandscape) onClose?.();
      };
    
      const handleSave = () => {
        setDuration(localDuration);
        if (isLandscape) onClose?.();
        else setBottomSheet(false);
      };
    
      const content = (
        <>
          <DurationContent
            isLandscape={isLandscape}
            availableDurationTypes={availableDurationTypes}
            selectedTabType={selectedTabType}
            selectedValue={selectedValue}
            onSelectTab={setSelectedTabType}
            onValueSelect={handleValueSelect}
            onValueClick={handleValueClick}
          />
          {!isLandscape && (
            <div className="w-full p-3">
              <PrimaryButton className="rounded-3xl" onClick={handleSave}>
                Save
              </PrimaryButton>
            </div>
          )}
        </>
      );
    
      return isLandscape ? (
        <DesktopTradeFieldCard className="p-0">
          <div className="w-[368px]">{content}</div>
        </DesktopTradeFieldCard>
      ) : (
        <div className="flex flex-col h-full">{content}</div>
      );
    };

These steps help isolate state logic and UI rendering, reducing component complexity and making the code easier to maintain.

};
const handleHoursSelect = (newHours: number) => {
lastValidHours.current = newHours;
if (isInitialRender.current) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider extracting common helper functions for hours and minutes updates to reduce code duplication and improve readability, differentiating select vs. click in one place.

You’re handling very similar logic twice for hours (and likewise for minutes). Consider extracting a common helper for hours updates so you only handle the differences (select vs. click) in one place.

For example, you can refactor the hours handlers as follows:

const updateHours = (
  newHours: number,
  callback: (value: string) => void
) => {
  lastValidHours.current = newHours;
  if (!isInitialRender.current) {
    lastValidMinutes.current = 0;
    callback(`${newHours}:00`);
  } else {
    callback(`${newHours}:${lastValidMinutes.current}`);
  }
  scrollToZeroMinutes();
};

const handleHoursSelect = (newHours: number) =>
  updateHours(newHours, onValueSelect);

const handleHoursClick = (newHours: number) =>
  onValueClick && updateHours(newHours, onValueClick);

Similarly, if the minutes handlers are similar, you could extract a helper as well:

const updateMinutes = (
  newMinutes: number,
  callback: (value: string) => void
) => {
  lastValidMinutes.current = newMinutes;
  callback(`${lastValidHours.current}:${newMinutes}`);
};

const handleMinutesSelect = (newMinutes: number) =>
  updateMinutes(newMinutes, onValueSelect);

const handleMinutesClick = (newMinutes: number) =>
  onValueClick && updateMinutes(newMinutes, onValueClick);

This reduces duplication while keeping functionality intact.

const finalResult: DurationRangesResponse = {} as DurationRangesResponse;

// Add each duration type only if it has a valid range
if (result.ticks) finalResult.ticks = result.ticks;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Use block braces for ifs, whiles, etc. (use-braces)

Suggested change
if (result.ticks) finalResult.ticks = result.ticks;
if (result.ticks) {


ExplanationIt is recommended to always use braces and create explicit statement blocks.

Using the allowed syntax to just write a single statement can lead to very confusing
situations, especially where subsequently a developer might add another statement
while forgetting to add the braces (meaning that this wouldn't be included in the condition).


// Add each duration type only if it has a valid range
if (result.ticks) finalResult.ticks = result.ticks;
if (result.seconds) finalResult.seconds = result.seconds;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Use block braces for ifs, whiles, etc. (use-braces)

Suggested change
if (result.seconds) finalResult.seconds = result.seconds;
if (result.seconds) {


ExplanationIt is recommended to always use braces and create explicit statement blocks.

Using the allowed syntax to just write a single statement can lead to very confusing
situations, especially where subsequently a developer might add another statement
while forgetting to add the braces (meaning that this wouldn't be included in the condition).

}, 100);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Use block braces for ifs, whiles, etc. (use-braces)

Suggested change
if (!container) return;
if (!container) {


ExplanationIt is recommended to always use braces and create explicit statement blocks.

Using the allowed syntax to just write a single statement can lead to very confusing
situations, especially where subsequently a developer might add another statement
while forgetting to add the braces (meaning that this wouldn't be included in the condition).

}

const range = durationConfig.ranges[type];
if (!range) return [];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Use block braces for ifs, whiles, etc. (use-braces)

Suggested change
if (!range) return [];
if (!range) {


ExplanationIt is recommended to always use braces and create explicit statement blocks.

Using the allowed syntax to just write a single statement can lead to very confusing
situations, especially where subsequently a developer might add another statement
while forgetting to add the braces (meaning that this wouldn't be included in the condition).

@@ -141,8 +131,8 @@ export function generateDurationValues(
* @returns Special case key (e.g., '24h') or empty string
*/
export function getSpecialCaseKey(hour?: number): string {
if (hour === undefined) return "";
return SPECIAL_HOUR_CASES[hour]?.key || "";
if (hour === undefined) return "";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Use block braces for ifs, whiles, etc. (use-braces)

Suggested change
if (hour === undefined) return "";
if (hour === undefined) {


ExplanationIt is recommended to always use braces and create explicit statement blocks.

Using the allowed syntax to just write a single statement can lead to very confusing
situations, especially where subsequently a developer might add another statement
while forgetting to add the braces (meaning that this wouldn't be included in the condition).

return value >= range.min && value <= range.max;
export function isValidDuration(type: keyof DurationRangesResponse, value: number): boolean {
const range = durationConfig.ranges[type];
if (!range) return false;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Use block braces for ifs, whiles, etc. (use-braces)

Suggested change
if (!range) return false;
if (!range) {


ExplanationIt is recommended to always use braces and create explicit statement blocks.

Using the allowed syntax to just write a single statement can lead to very confusing
situations, especially where subsequently a developer might add another statement
while forgetting to add the braces (meaning that this wouldn't be included in the condition).

if (!range) return 0;
return type === "minutes" ? 1 : range.min;
const range = durationConfig.ranges[type];
if (!range) return 0;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Use block braces for ifs, whiles, etc. (use-braces)

Suggested change
if (!range) return 0;
if (!range) {


ExplanationIt is recommended to always use braces and create explicit statement blocks.

Using the allowed syntax to just write a single statement can lead to very confusing
situations, especially where subsequently a developer might add another statement
while forgetting to add the braces (meaning that this wouldn't be included in the condition).

Copy link

@devloai devloai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reviewed the changes that replace product_type with product_id in the product configuration API. While the implementation is mostly complete, there are critical issues that need to be addressed before merging:

  1. TradeFormController.tsx still uses trade_type instead of product_id when calling fetchProductConfig, which will cause runtime errors
  2. The account_uuid parameter is required in the API but passed as an empty string - it should use the client store's selectedAccountId
  3. Make sure all components using this hook have been updated to use the new parameter names

Please address these issues to ensure a smooth transition to the new API parameters.

Feel free to provide me with any instructions or feedback related to this review or other repository tasks, which I'll take into account for future work.

const config = await getProductConfig({
instrument_id,
product_id,
account_uuid: "", // TODO - get the actual account_uuid
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The account_uuid parameter is currently passed as an empty string with a TODO comment. You should use the selectedAccountId from the client store instead:

Suggested change
account_uuid: "", // TODO - get the actual account_uuid
account_uuid: useClientStore.getState().selectedAccountId, // Get account ID from client store

setConfigError(null);

// Create a cache key
const cacheKey = `${product_id}_${instrument_id}`;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cache key is now created using product_id instead of product_type. This is correct, but there's an issue in TradeFormController.tsx (line 74) which still calls this function with trade_type instead of product_id. This will cause runtime errors when the PR is merged.

params: {
instrument_id,
product_id,
account_uuid: "",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The account_uuid parameter is being passed as an empty string. You should use useClientStore.getState().selectedAccountId to get the actual account UUID from the client store.

const defaultStake = adaptDefaultStake(config);
setStake(defaultStake);
setAllowEquals(config.data.defaults.allow_equals);
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function signature has been updated to use product_id instead of product_type, but TradeFormController.tsx (line 74) still calls this function with trade_type instead of product_id. This will cause runtime errors when the PR is merged.

instrument_id: string;
instrument_id: string;
product_id: string;
account_uuid: string;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The account_uuid parameter has been added as required in the ProductConfigRequest interface, but it's being passed as an empty string in the implementation. You should use useClientStore.getState().selectedAccountId to populate this value.


// Create a cache key
const cacheKey = `${product_id}_${instrument_id}`;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR introduces a breaking change by changing product_type to product_id in the API interface. You need to update TradeFormController.tsx which still uses trade_type instead of product_id when calling this function. Also verify that the tradeTypeConfigs object keys (like 'rise_fall') match valid product_id values.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant