Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
298920c
id:27 feat(visible-prompt): wip
mrbalov Feb 17, 2026
80ccbfd
id:27 feat(visible-prompt): wip
mrbalov Feb 17, 2026
363c3dc
id:27 feat(visible-prompt): wip
mrbalov Feb 17, 2026
3090c54
Merge branch 'main' of github.com:mrbalov/strava-activity-image-gener…
mrbalov Feb 17, 2026
6c35aca
Merge branch 'main' of github.com:mrbalov/strava-activity-image-gener…
mrbalov Feb 19, 2026
45cdf88
Merge branch 'main' of github.com:mrbalov/strava-activity-image-gener…
mrbalov Feb 19, 2026
11dae11
Merge branch 'main' of github.com:mrbalov/strava-activity-image-gener…
mrbalov Feb 20, 2026
fbab534
Merge branch 'main' of github.com:mrbalov/strava-activity-image-gener…
mrbalov Feb 20, 2026
c4ae292
Merge branch 'main' of github.com:mrbalov/strava-activity-image-gener…
mrbalov Feb 20, 2026
7d0448e
Merge branch 'main' of github.com:mrbalov/strava-activity-image-gener…
mrbalov Feb 20, 2026
19138b1
Merge branch 'main' of github.com:mrbalov/strava-activity-image-gener…
mrbalov Feb 23, 2026
5fdd4ce
Merge branch 'main' of github.com:mrbalov/strava-activity-image-gener…
mrbalov Feb 23, 2026
468908a
Merge branch 'main' of github.com:mrbalov/strava-activity-image-gener…
mrbalov Feb 23, 2026
3cdb3fc
Merge branch 'main' of github.com:mrbalov/strava-activity-image-gener…
mrbalov Feb 23, 2026
6785664
Merge branch 'main' of github.com:mrbalov/strava-activity-image-gener…
mrbalov Feb 24, 2026
d5085ca
id:65 feat(tanstack-query): improvements
mrbalov Feb 24, 2026
3507a23
fix: revert incorrect changelog entries
mrbalov Feb 24, 2026
64ad3f9
id:65 feat(tanstack-query): improvements
mrbalov Feb 24, 2026
13608ce
id:65 feat(tanstack-query): improvements
mrbalov Feb 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,28 @@ Please, document here only changes visible to the client app.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [5.4.1] - 2026-02-24

### [65 Improved TanStack Query Integration and Error Handling](https://github.com/mrbalov/pace/issues/65)

### Changed

- Refactored all custom TanStack Query hooks to use native `useQuery` hook with built-in state management
- Updated component props from `isLoaded` boolean to `isError` boolean for clearer semantic meaning and better error handling
- Simplified API response types by removing intermediate `ResponseImage` interface and accessing data directly
- Improved query key generation to include API endpoint paths for better cache invalidation
- Enhanced error state handling across Image, Prompt, and Signals components

### Fixed

- Fixed typo in ExpandableCard component: "Someting" → "Something"
- Corrected getStatus logic to properly handle content display when data is available but no error occurred

### Removed

- Custom loading/loaded state management in favor of TanStack Query's built-in `isLoading`, `isError` states
- Redundant `ResponseImage` type interface, now using direct string return type for image data

## [5.4.0] - 2026-02-24

### [65 Refactored API Layer to TanStack Query Naming Conventions](https://github.com/mrbalov/pace/issues/65)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "torq",
"version": "5.4.0",
"version": "5.4.1",
"description": "Generates AI images based on Strava activity data.",
"type": "module",
"private": true,
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/api/authStatus/queryAuthStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { API_ENDPOINTS } from '../constants';

/**
* Query authentication status.
* @returns {Promise<boolean>} Authentication status.
* @returns {Promise<boolean | null>} Authentication status.
*/
const queryAuthStatus = async (): Promise<boolean> => {
const queryAuthStatus = async (): Promise<boolean | null> => {
try {
const response = await client<Response>(
API_ENDPOINTS.AUTH_STATUS,
Expand Down
6 changes: 3 additions & 3 deletions packages/ui/src/api/authStatus/useQueryAuthStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import { API_ENDPOINTS } from '../constants';
* @returns {object} Authentication status data.
*/
const useQueryAuthStatus = () =>
useQuery({
useQuery<boolean | null>({
queryKey: [API_ENDPOINTS.AUTH_STATUS],
/**
* Queries authentication status from the internal API.
* @returns {Promise<boolean>} Authentication status.
* @returns {Promise<boolean | null>} Authentication status.
*/
queryFn: async (): Promise<boolean> => {
queryFn: async (): Promise<boolean | null> => {
const { default: queryAuthStatus } = await import('./queryAuthStatus');

return queryAuthStatus();
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/api/logout/logout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { API_ENDPOINTS } from '../constants';

/**
* Logs out the user by calling the backend logout endpoint.
* This will clear HTTP-only cookies on the server side.
* Clears HTTP-only cookies on the server side.
* @returns {Promise<void>} Promise that resolves when logout is complete.
*/
const logout = async (): Promise<void> => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { API_ENDPOINTS } from '../constants';

/**
* Queries Strava activities.
* @returns {Promise<StravaActivity[]>} Strava activities.
* @returns {Promise<StravaActivity[] | null>} Strava activities.
*/
const queryStravaActivities = (): Promise<StravaActivity[] | null> =>
client<StravaActivity[]>(API_ENDPOINTS.STRAVA_ACTIVITIES);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useQuery } from '@tanstack/react-query';
import { StravaActivity } from '@torq/strava-api';

import { API_ENDPOINTS } from '../constants';

Expand All @@ -7,11 +8,11 @@ import { API_ENDPOINTS } from '../constants';
* @returns {object} Strava activities data.
*/
const useQueryStravaActivities = () =>
useQuery({
useQuery<StravaActivity[] | null>({
queryKey: [API_ENDPOINTS.STRAVA_ACTIVITIES],
/**
* Queries Strava activities from the internal API.
* @returns {Promise<StravaActivity[]>} Strava activities.
* @returns {Promise<StravaActivity[] | null>} Strava activities.
*/
queryFn: async () => {
const { default: queryStravaActivities } = await import('./queryStravaActivities');
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/api/stravaActivity/queryStravaActivity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import client from '../client';
/**
* Query specific activity by ID.
* @param {string} id - Activity ID
* @returns {Promise<StravaActivity>} Activity data
* @returns {Promise<StravaActivity | null>} Activity data
*/
const queryStravaActivity = (id: string): Promise<StravaActivity | null> =>
client<StravaActivity>(API_ENDPOINTS.STRAVA_ACTIVITY(id));
client<StravaActivity | null>(API_ENDPOINTS.STRAVA_ACTIVITY(id));

export default queryStravaActivity;
5 changes: 3 additions & 2 deletions packages/ui/src/api/stravaActivity/useQueryStravaActivity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useQuery } from '@tanstack/react-query';
import { StravaActivity } from '@torq/strava-api';

import { API_ENDPOINTS } from '../constants';

Expand All @@ -8,11 +9,11 @@ import { API_ENDPOINTS } from '../constants';
* @returns {object} Strava activity data.
*/
const useQueryStravaActivity = (activityId: string) =>
useQuery({
useQuery<StravaActivity | null>({
queryKey: [API_ENDPOINTS.STRAVA_ACTIVITY(activityId)],
/**
* Queries a specific Strava activity from the internal API.
* @returns {Promise<StravaActivity>} Strava activity.
* @returns {Promise<StravaActivity | null>} Strava activity.
*/
queryFn: async () => {
const { default: queryStravaActivity } = await import('./queryStravaActivity');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import client from '../client';
import { API_ENDPOINTS } from '../constants';
import { Input, Response, ResponseImage } from './types';
import { Input, Response } from './types';

/**
* Generates a Strava activity image.
* @param {Input} input - The input parameters for generating the image.
* @param {string} input.activityId - The ID of the activity to generate an image for.
* @param {string} input.prompt - The prompt to use for image generation.
* @returns {Promise<ResponseImage | null>} The generated image data or null if not available.
* @returns {Promise<string | null>} The generated image data or null if not available.
*/
const queryStravaActivityImage = async ({
activityId,
prompt,
}: Input): Promise<ResponseImage | null> => {
}: Input): Promise<string | null> => {
const response = await client<Response>(
API_ENDPOINTS.STRAVA_ACTIVITY_IMAGE_GENERATOR(activityId, prompt),
);

return response?.image ?? null;
return response?.image?.imageData ?? null;
}

export default queryStravaActivityImage;
12 changes: 5 additions & 7 deletions packages/ui/src/api/stravaActivityImage/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@ export interface Input {
prompt: string;
}

export interface ResponseImage {
imageData?: string;
usedFallback?: boolean;
retriesPerformed?: number;
}

export interface Response {
image?: ResponseImage;
image?: {
imageData?: string;
usedFallback?: boolean;
retriesPerformed?: number;
};
}

export interface Options {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
'use client';

import { useState, useEffect } from 'react';
import { useQuery } from '@tanstack/react-query';

import queryStravaActivityImage from './queryStravaActivityImage';
import { Input, Options, ResponseImage } from './types';
import { Input, Options } from './types';
import { API_ENDPOINTS } from '../constants';

/**
* Generates a Strava activity image.
Expand All @@ -22,36 +23,33 @@ const useQueryStravaActivityImage = (
{
skip = false,
}: Options = {},
) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const [isLoaded, setIsLoaded] = useState<boolean>(false);
const [data, setData] = useState<ResponseImage | null>(null);

useEffect(() => {
if (!isLoading && !isLoaded && activityId && prompt && !skip) {
setIsLoading(true);

queryStravaActivityImage({ activityId, prompt })
.then((response) => {
setData(response);
setIsLoaded(true);
})
.catch((error) => {
console.error('Error generating Strava activity image:', error);
setData(null);
})
.finally(() => {
setIsLoading(false);
setIsLoaded(true);
) =>
useQuery<string | null>({
queryKey: [
API_ENDPOINTS.STRAVA_ACTIVITY_IMAGE_GENERATOR(
activityId ?? '',
prompt ?? '',
),
],
/**
* Queries Strava activity image generator from the internal API.
* @returns {Promise<string | null>} Strava activity image.
*/
queryFn: () => {
if (activityId && prompt) {
return queryStravaActivityImage({
activityId,
prompt,
});
}
}, [activityId, prompt, skip]);

return {
isLoading,
isLoaded,
data,
};
};
} else {
return null;
}
},
enabled: (
Boolean(activityId)
&& Boolean(prompt)
&& !skip
),
});

export default useQueryStravaActivityImage;
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import client from '../client';
import { API_ENDPOINTS } from '../constants';
import toBase64 from './toBase64';
import { Input, Response } from './types';

/**
Expand All @@ -13,9 +14,11 @@ const queryActivityImageGenerationPrompt = async ({
activityId,
activitySignals,
}: Input): Promise<string | null> => {
const signalsBase64 = btoa(JSON.stringify(activitySignals));
const response = await client<Response>(
API_ENDPOINTS.STRAVA_ACTIVITY_IMAGE_GENERATION_PROMPT(activityId, signalsBase64),
API_ENDPOINTS.STRAVA_ACTIVITY_IMAGE_GENERATION_PROMPT(
activityId,
toBase64(activitySignals),
),
);

return response?.prompt || null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './toBase64';
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { StravaActivitySignals } from '@torq/get-strava-activity-signals';

/**
* Converts Strava activity signals to a Base64-encoded string.
* @param {StravaActivitySignals} input - The activity signals to be encoded.
* @returns {string} Base64-encoded string.
*/
const toBase64 = (input: StravaActivitySignals): string =>
btoa(JSON.stringify(input));

export default toBase64;
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
'use client';

import { useEffect, useState } from 'react';
import { useQuery } from '@tanstack/react-query';

import queryActivityImageGenerationPrompt from './queryStravaActivityImageGenerationPrompt';
import { Input } from './types';
import toBase64 from './toBase64';
import { API_ENDPOINTS } from '../constants';
import queryActivityImageGenerationPrompt from './queryStravaActivityImageGenerationPrompt';

/**
* Queries Strava activity image generation prompt.
Expand All @@ -15,36 +17,32 @@ import { Input } from './types';
const useQueryStravaActivityImageGenerationPrompt = ({
activityId,
activitySignals,
}: Partial<Input>) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const [isLoaded, setIsLoaded] = useState<boolean>(false);
const [data, setData] = useState<string | null>(null);

useEffect(() => {
if (!isLoading && !isLoaded && activityId && activitySignals) {
setIsLoading(true);

queryActivityImageGenerationPrompt({ activityId, activitySignals })
.then((response) => {
setData(response);
setIsLoaded(true);
})
.catch((error) => {
console.error('Error querying Strava activity image generation prompt:', error);
setData(null);
})
.finally(() => {
setIsLoading(false);
setIsLoaded(true);
}: Partial<Input>) =>
useQuery<string | null>({
queryKey: [
API_ENDPOINTS.STRAVA_ACTIVITY_IMAGE_GENERATION_PROMPT(
activityId ?? '',
activitySignals ? toBase64(activitySignals) : '',
),
],
/**
* Queries Strava activity image generation prompt from the internal API.
* @returns {Promise<string | null>} Activity image generation prompt.
*/
queryFn: () => {
if (activityId && activitySignals) {
return queryActivityImageGenerationPrompt({
activityId,
activitySignals,
});
}
}, [activityId, activitySignals]);

return {
isLoading,
isLoaded,
data,
};
};
} else {
return null;
}
},
enabled: (
Boolean(activityId)
&& Boolean(activitySignals)
),
});

export default useQueryStravaActivityImageGenerationPrompt;
Loading