feat: watchlist (premium), mobile bottom nav
Build & Push Docker Image / build (push) Successful in 2m35s
Build & Push Docker Image / build (push) Successful in 2m35s
This commit is contained in:
@@ -335,6 +335,7 @@ export const UserPreferencesDensity = {
|
||||
export interface UserPreferences {
|
||||
view?: UserPreferencesView;
|
||||
density?: UserPreferencesDensity;
|
||||
watchlist?: number[];
|
||||
}
|
||||
|
||||
export interface ErrorResponse {
|
||||
|
||||
@@ -2199,6 +2199,83 @@ export const useUpdateMePreferences = <TError = ErrorType<ErrorResponse>,
|
||||
return useMutation(getUpdateMePreferencesMutationOptions(options));
|
||||
}
|
||||
|
||||
export const getGetMeWatchlistUrl = () => {
|
||||
|
||||
|
||||
|
||||
|
||||
return `/api/auth/me/watchlist`
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get current user's watchlist tools (premium)
|
||||
*/
|
||||
export const getMeWatchlist = async ( options?: RequestInit): Promise<ToolWithStats[]> => {
|
||||
|
||||
return customFetch<ToolWithStats[]>(getGetMeWatchlistUrl(),
|
||||
{
|
||||
...options,
|
||||
method: 'GET'
|
||||
|
||||
|
||||
}
|
||||
);}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const getGetMeWatchlistQueryKey = () => {
|
||||
return [
|
||||
`/api/auth/me/watchlist`
|
||||
] as const;
|
||||
}
|
||||
|
||||
|
||||
export const getGetMeWatchlistQueryOptions = <TData = Awaited<ReturnType<typeof getMeWatchlist>>, TError = ErrorType<ErrorResponse>>( options?: { query?:UseQueryOptions<Awaited<ReturnType<typeof getMeWatchlist>>, TError, TData>, request?: SecondParameter<typeof customFetch>}
|
||||
) => {
|
||||
|
||||
const {query: queryOptions, request: requestOptions} = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetMeWatchlistQueryKey();
|
||||
|
||||
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getMeWatchlist>>> = ({ signal }) => getMeWatchlist({ signal, ...requestOptions });
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof getMeWatchlist>>, TError, TData> & { queryKey: QueryKey }
|
||||
}
|
||||
|
||||
export type GetMeWatchlistQueryResult = NonNullable<Awaited<ReturnType<typeof getMeWatchlist>>>
|
||||
export type GetMeWatchlistQueryError = ErrorType<ErrorResponse>
|
||||
|
||||
|
||||
/**
|
||||
* @summary Get current user's watchlist tools (premium)
|
||||
*/
|
||||
|
||||
export function useGetMeWatchlist<TData = Awaited<ReturnType<typeof getMeWatchlist>>, TError = ErrorType<ErrorResponse>>(
|
||||
options?: { query?:UseQueryOptions<Awaited<ReturnType<typeof getMeWatchlist>>, TError, TData>, request?: SecondParameter<typeof customFetch>}
|
||||
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
|
||||
const queryOptions = getGetMeWatchlistQueryOptions(options)
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & { queryKey: QueryKey };
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const getListUsersUrl = () => {
|
||||
|
||||
|
||||
|
||||
@@ -656,6 +656,33 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
|
||||
/auth/me/watchlist:
|
||||
get:
|
||||
operationId: getMeWatchlist
|
||||
tags: [auth]
|
||||
summary: Get current user's watchlist tools (premium)
|
||||
responses:
|
||||
"200":
|
||||
description: Watchlist tools in saved order
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ToolWithStats"
|
||||
"401":
|
||||
description: Not authenticated
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
"403":
|
||||
description: Premium feature required
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
|
||||
/users:
|
||||
get:
|
||||
operationId: listUsers
|
||||
@@ -1193,6 +1220,10 @@ components:
|
||||
density:
|
||||
type: string
|
||||
enum: [cozy, compact]
|
||||
watchlist:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
|
||||
@@ -485,7 +485,8 @@ export const GetMeResponse = zod.object({
|
||||
*/
|
||||
export const GetMePreferencesResponse = zod.object({
|
||||
"view": zod.enum(['grid', 'table', 'rows']).optional(),
|
||||
"density": zod.enum(['cozy', 'compact']).optional()
|
||||
"density": zod.enum(['cozy', 'compact']).optional(),
|
||||
"watchlist": zod.array(zod.number()).optional()
|
||||
})
|
||||
|
||||
|
||||
@@ -494,15 +495,40 @@ export const GetMePreferencesResponse = zod.object({
|
||||
*/
|
||||
export const UpdateMePreferencesBody = zod.object({
|
||||
"view": zod.enum(['grid', 'table', 'rows']).optional(),
|
||||
"density": zod.enum(['cozy', 'compact']).optional()
|
||||
"density": zod.enum(['cozy', 'compact']).optional(),
|
||||
"watchlist": zod.array(zod.number()).optional()
|
||||
})
|
||||
|
||||
export const UpdateMePreferencesResponse = zod.object({
|
||||
"view": zod.enum(['grid', 'table', 'rows']).optional(),
|
||||
"density": zod.enum(['cozy', 'compact']).optional()
|
||||
"density": zod.enum(['cozy', 'compact']).optional(),
|
||||
"watchlist": zod.array(zod.number()).optional()
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* @summary Get current user's watchlist tools (premium)
|
||||
*/
|
||||
export const GetMeWatchlistResponseItem = zod.object({
|
||||
"id": zod.number(),
|
||||
"name": zod.string(),
|
||||
"description": zod.string(),
|
||||
"category": zod.string(),
|
||||
"websiteUrl": zod.string().nullish(),
|
||||
"iconUrl": zod.string().nullish(),
|
||||
"createdBy": zod.string().nullish(),
|
||||
"features": zod.array(zod.string()).optional(),
|
||||
"tags": zod.array(zod.string()).optional(),
|
||||
"createdAt": zod.coerce.date(),
|
||||
"updatedAt": zod.coerce.date(),
|
||||
"ratingCount": zod.number(),
|
||||
"avgUsefulness": zod.number().nullable(),
|
||||
"avgUsability": zod.number().nullable(),
|
||||
"avgCombined": zod.number().nullable()
|
||||
})
|
||||
export const GetMeWatchlistResponse = zod.array(GetMeWatchlistResponseItem)
|
||||
|
||||
|
||||
/**
|
||||
* @summary List all local users (admin only)
|
||||
*/
|
||||
|
||||
@@ -11,4 +11,5 @@ import type { UserPreferencesView } from './userPreferencesView';
|
||||
export interface UserPreferences {
|
||||
view?: UserPreferencesView;
|
||||
density?: UserPreferencesDensity;
|
||||
watchlist?: number[];
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { z } from "zod/v4";
|
||||
export type UserPreferences = {
|
||||
view?: "grid" | "table" | "rows";
|
||||
density?: "cozy" | "compact";
|
||||
watchlist?: number[];
|
||||
};
|
||||
|
||||
export const usersTable = pgTable("users", {
|
||||
|
||||
Reference in New Issue
Block a user