Skip to content

Commit

Permalink
refactor: query and mutation, get zod schema defaults helper
Browse files Browse the repository at this point in the history
  • Loading branch information
rifandani committed Oct 2, 2024
1 parent 87c7faa commit cb01528
Show file tree
Hide file tree
Showing 15 changed files with 473 additions and 115 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"ci": "biome ci ./src",
"typecheck": "tsc --noEmit",
"test:install": "playwright install --with-deps chromium",
"test": "playwright test",
"test": "DEBUG=pw:api playwright test",
"test:ui": "playwright test --ui",
"test:report": "playwright show-report",
"regression": "run-p check typecheck test",
Expand Down
7 changes: 6 additions & 1 deletion src/modules/auth/apis/auth.api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { http } from '#shared/services/http.service';
import type { Options } from 'ky';
import { z } from 'zod';

// #region API SCHEMAS
Expand Down Expand Up @@ -36,7 +37,10 @@ export const authRepositories = {
* @access public
* @url POST ${env.apiBaseUrl}/auth/login
*/
login: async ({ json }: { json: AuthLoginRequestSchema }) => {
login: async (
{ json }: { json: AuthLoginRequestSchema },
options?: Options,
) => {
const resp = await http.instance
.post('auth/login', {
json,
Expand All @@ -57,6 +61,7 @@ export const authRepositories = {
},
],
},
...options,
})
.json();

Expand Down
54 changes: 0 additions & 54 deletions src/modules/auth/hooks/use-auth-user-store.hook.ts

This file was deleted.

117 changes: 117 additions & 0 deletions src/modules/auth/hooks/use-auth-user-store.hook.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { authLoginResponseSchema } from '#auth/apis/auth.api';
import { isFunction } from '@rifandani/nxact-yutiriti';
import React from 'react';
import { z } from 'zod';
import { create, createStore, useStore } from 'zustand';
import { createJSONStorage, devtools, persist } from 'zustand/middleware';

export type UserStoreState = z.infer<typeof userStoreStateSchema>;
export type UserStore = z.infer<typeof userStoreSchema>;
export type UserStoreLocalStorage = z.infer<typeof userStoreLocalStorageSchema>;

export const userStoreName = 'app-user' as const;
const userStoreStateSchema = z.object({
user: authLoginResponseSchema.nullable(),
});
const userStoreActionSchema = z.object({
setUser: z.function().args(authLoginResponseSchema).returns(z.void()),
clearUser: z.function().args(z.void()).returns(z.void()),
});
export const userStoreSchema = userStoreStateSchema.merge(
userStoreActionSchema,
);
export const userStoreLocalStorageSchema = z.object({
state: userStoreStateSchema,
version: z.number(),
});

/**
* Hooks to manipulate user store
*
* @example
*
* ```tsx
* const { user, setUser, clearUser } = useUserStore()
* ```
*/
export const useAuthUserStore = create<UserStore>()(
devtools(
persist(
(set) => ({
user: null,

setUser: (user) => {
set({ user });
},
clearUser: () => {
set({ user: null });
},
}),
{
name: userStoreName, // name of the item in the storage (must be unique)
version: 0, // a migration will be triggered if the version in the storage mismatches this one
storage: createJSONStorage(() => localStorage), // by default, 'localStorage' is used
},
),
),
);

/**
* for use with react context to initialize the store with props (default state)
*
* @link https://docs.pmnd.rs/zustand/guides/initialize-state-with-props
*/
const createUserStore = (initialState?: Partial<UserStoreState>) => {
return createStore<UserStore>()(
devtools((set) => ({
user: null,
...initialState,

setUser: (user) => {
set({ user });
},
clearUser: () => {
set({ user: null });
},
})),
);
};

export const UserContext = React.createContext<ReturnType<
typeof createUserStore
> | null>(null);

export function useUserContext<T>(selector: (_store: UserStore) => T): T {
const store = React.useContext(UserContext);
if (!store) throw new Error('Missing UserContext.Provider in the tree');

return useStore(store, selector);
}

/**
* for use with react context to initialize the store with props (default state)
*
* @link https://docs.pmnd.rs/zustand/guides/initialize-state-with-props
*/
export function UserProvider({
children,
initialState,
}: {
children:
| React.ReactNode
| ((context: ReturnType<typeof createUserStore>) => JSX.Element);
initialState?: Parameters<typeof createUserStore>[0];
}) {
const storeRef = React.useRef<ReturnType<typeof createUserStore> | null>(
null,
);
if (!storeRef.current) {
storeRef.current = createUserStore(initialState);
}

return (
<UserContext.Provider value={storeRef.current}>
{isFunction(children) ? children(storeRef.current) : children}
</UserContext.Provider>
);
}
6 changes: 3 additions & 3 deletions src/modules/shared/apis/cdn.api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ky, { type KyResponse } from 'ky';
import ky, { type KyResponse, type Options } from 'ky';

export type GetCdnFileRequestSchema = {
url: string;
Expand Down Expand Up @@ -26,8 +26,8 @@ export const cdnRepositories = {
* @access public
* @note could throw error in the shape of `HTTPError` error
*/
async getCdnFile({ url }: { url: string }) {
const response = await ky.get(url);
async getCdnFile({ url }: { url: string }, options?: Options) {
const response = await ky.get(url, options);
const blob = await response.blob();
const headers = Object.fromEntries(response.headers);

Expand Down
4 changes: 2 additions & 2 deletions src/modules/shared/hooks/use-cdn-file-mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type Opt = {
*
* Includes error handling in `onError` for convenience.
*/
export function useDownloadCdnFile(
export function useCdnFileMutation(
opt: Opt,
mutationOptions?: Except<
UseMutationOptions<GetCdnFileSuccessSchema, HTTPError, string>,
Expand All @@ -50,7 +50,7 @@ export function useDownloadCdnFile(
/**
* Get mutation state based on the mutation key.
*/
export function useDownloadCdnFileMutationState(opt: Opt) {
export function useCdnFileMutationState(opt: Opt) {
return useMutationState<
MutationState<GetCdnFileSuccessSchema, HTTPError, string>
>({
Expand Down
15 changes: 6 additions & 9 deletions src/modules/shared/hooks/use-cdn-file-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type Opt = {
/**
* Eagerly download (GET) file based on input url.
*
* Includes error handling in effect for convenience.
* Includes error handling in "effect" for convenience.
*/
export function useCdnFileQuery(
opt: Opt,
Expand All @@ -32,15 +32,12 @@ export function useCdnFileQuery(
'queryKey' | 'queryFn'
>,
) {
const queryKey = cdnKeys[opt.key](opt.url);
const queryFn = opt.url
? () => cdnRepositories.getCdnFile({ url: opt.url as string })
: skipToken;

const query = useQuery({
queryKey,
queryFn,
enabled: !!opt.url,
queryKey: cdnKeys[opt.key](opt.url),
queryFn: opt.url
? ({ signal }) =>
cdnRepositories.getCdnFile({ url: opt.url as string }, { signal })
: skipToken,
...(queryOptions && queryOptions),
});

Expand Down
75 changes: 75 additions & 0 deletions src/modules/shared/hooks/use-feature-flag-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { getSchemaDefaults } from '#shared/utils/helper.util';
import { z } from 'zod';
import { create } from 'zustand';
import { createJSONStorage, devtools, persist } from 'zustand/middleware';

const defaultValue = import.meta.env.VITE_MODE === 'development';
const featureFlagStoreName = 'react-app-feature-flag' as const;
const featureFlagStoreStateSchema = z.object({
auth: z.object({
condition: z.object({
service_checking: z.boolean().default(defaultValue),
}),
endpoint: z.object({
list_user_access: z.boolean().default(defaultValue),
}),
route: z.object({
'/user-access/role': z.boolean().default(defaultValue),
}),
ui: z.object({
logout_button: z.boolean().default(defaultValue),
}),
}),
});

export type FeatureFlagStoreStateSchema = z.infer<
typeof featureFlagStoreStateSchema
>;
export type FeatureFlagStoreActionSchema = {
reset: () => void;
};
export type FeatureFlagStoreSchema = z.infer<
typeof featureFlagStoreStateSchema
> &
FeatureFlagStoreActionSchema;

export const featureFlagStoreInitialState = getSchemaDefaults<
typeof featureFlagStoreStateSchema
>(featureFlagStoreStateSchema);

/**
* Persisted react-app-feature-flag store.
*
* @example
*
* ```tsx
* const reset = useFeatureFlagStore(state => state.reset)
* ```
*/
export const useFeatureFlagStore = create<FeatureFlagStoreSchema>()(
devtools(
persist(
(set) => ({
...featureFlagStoreInitialState,

reset: () => {
set(featureFlagStoreInitialState);
},
}),
{
name: featureFlagStoreName, // name of the item in the storage (must be unique)
storage: createJSONStorage(() => localStorage), // by default, 'localStorage' is used
version: 0, // a migration will be triggered if the version in the storage mismatches this one
// migrate: (persistedState, version) => {
// if (version === 0) {
// // if the stored value is in version 0, we rename the field to the new name
// persistedState.newField = persistedState.oldField;
// delete persistedState.oldField;
// }

// return persistedState;
// },
},
),
),
);
Loading

0 comments on commit cb01528

Please sign in to comment.