Skip to content

Commit

Permalink
Running build using Core Symlink
Browse files Browse the repository at this point in the history
  • Loading branch information
gigincg committed Dec 5, 2024
1 parent 5ed9fd5 commit 3038582
Show file tree
Hide file tree
Showing 19 changed files with 2,416 additions and 56 deletions.
17 changes: 17 additions & 0 deletions .cursorrules
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
You are an expert developer specializing in React, Vite, and TypeScript, with deep knowledge of microfrontend architecture. You are assisting with development in a Vite-based project that will be used as a microfrontend module within the Open Health Care Network (OHCN) Care Frontend system (github.com/ohcnetwork/care_fe).

Key considerations for your responses:
1. All code should be written in TypeScript with proper type definitions
2. Follow React best practices and modern hooks-based patterns
3. Ensure compatibility with Vite's module federation for microfrontend architecture
4. Follow accessibility guidelines for healthcare applications
5. Use proper code organization that aligns with microfrontend architecture

When providing solutions:
- Include TypeScript types/interfaces where applicable
- Explain architectural decisions and their impact on the microfrontend setup
- Consider state management patterns that work well in a microfrontend context
- Provide guidance on testing strategies when relevant
- Include comments explaining complex logic or integration points

The code being developed will be part of a larger EMR system, so maintain focus on healthcare-specific requirements with the main Care Frontend application.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,6 @@ vite.config.ts.timestamp-*
# Temporary files
*.tmp
*.bak

# Ignore core symlink
/core
14 changes: 13 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"dependencies": {
"@livekit/components-react": "^2.6.2",
"@livekit/components-styles": "^1.1.3",
"livekit-client": "^2.5.5"
"livekit-client": "^2.5.5",
"raviger": "^4.1.2"
},
"peerDependencies": {
"react": "18.3.1",
Expand Down
144 changes: 144 additions & 0 deletions src/api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# CARE's data fetching utilities: `useQuery` and `request`

There are two main ways to fetch data in CARE: `useQuery` and `request`. Both of these utilities are built on top of [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch).

## `useQuery`

`useQuery` is a React hook that allows you to fetch data and automatically update the UI when the data changes. It is
a wrapper around `request` that is designed to be used in React components. Only "GET" requests are supported with `useQuery`. For other request methods (mutations), use `request`.

### Usage

```jsx
import { useQuery } from "@care/request";
import FooRoutes from "@foo/routes";

export default function FooDetails({ children, id }) {
const { res, data, loading, error } = useQuery(FooRoutes.getFoo, {
pathParams: { id },
});

/* 🪄 Here typeof data is automatically inferred from the specified route. */

if (loading) return <Loading />;

if (res.status === 403) {
navigate("/forbidden");
return null;
}

if (error) {
return <Error error={error} />;
}

return (
<div>
<span>{data.id}</span>
<span>{data.name}</span>
</div>
);
}
```

### API

```ts
useQuery(route: Route, options?: QueryOptions): ReturnType<useQuery>;
```

#### `route`

A route object that specifies the endpoint to fetch data from.

```ts
const FooRoutes = {
getFoo: {
path: "/api/v1/foo/{id}/", // 👈 The path to the endpoint. Slug parameters can be specified using curly braces.

method: "GET", // 👈 The HTTP method to use. Optional; defaults to "GET".
TRes: Type<Foo>(), // 👈 The type of the response body (for type inference).
TBody: Type<Foo>(), // 👈 The type of the request body (for type inference).
noAuth: true, // 👈 Whether to skip adding the Authorization header to the request.
},
} as const; // 👈 This is important for type inference to work properly.
```

#### `options`

An object that specifies options for the request.

```ts
const options = {
prefetch: true, // 👈 Whether to prefetch the data when the component mounts.
refetchOnWindowFocus: true, // 👈 Whether to refetch the data when the window regains focus.

// The following options are passed directly to the underlying `request` function.

pathParams: { id: "123" }, // 👈 The slug parameters to use in the path.
// If you accidentally forget to specify a slug parameter an error will be
// thrown before the request is made.

query: { limit: 10 }, // 👈 The query parameters to be added to the request URL.
body: { name: "foo" }, // 👈 The body to be sent with the request. Should be compatible with the TBody type of the route.
headers: { "X-Foo": "bar" }, // 👈 Additional headers to be sent with the request. (Coming soon...)

silent: true, // 👈 Whether to suppress notifications for this request.
// This is useful for requests that are made in the background.

reattempts: 3, // 👈 The number of times to retry the request if it fails.
// Reattempts are only made if the request fails due to a network error. Responses with
// status codes in the 400s and 500s are not retried.

onResponse: (res) => {
// 👈 An optional callback that is called after the response is received.
if (res.status === 403) {
navigate("/forbidden");
}
},
// This is useful for handling responses with status codes in the 400s and 500s for a specific request.
};
```

#### `ReturnType<useQuery>`

The `useQuery` hook returns an object with the following properties:

```ts
{
res: Type<TRes> | undefined; // 👈 The response object. `undefined` if the request has not been made yet.

data: TRes | null; // 👈 The response body. `null` if the request has not been made yet.

error: any; // 👈 The error that occurred while making the request if any.

loading: boolean; // 👈 Whether the request is currently in progress.

refetch: () => void; // 👈 A function that can be called to refetch the data.
// Ideal for revalidating stale data after a mutation.
}
```

## `request`

`request` is a function that allows you to fetch data. It is a wrapper around `fetch` that adds some useful features. It can be used in both React components and non-React code. For fetching data in React components, prefer using `useQuery`. For mutations, use `request`.

### `request` usage

```ts
import { request } from "@care/request";
import FooRoutes from "@foo/routes";

export default async function updateFoo(id: string, object: Foo) {
const { res, data } = await request(FooRoutes.updateFoo, {
pathParams: { id },
body: object, // 👈 The body is automatically serialized to JSON. Should be compatible with the TBody type of the route.
});

if (res.status === 403) {
navigate("/forbidden");
return null;
}

return data;
}
```
50 changes: 49 additions & 1 deletion src/api/api.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,46 @@
import { Type } from "@/Redux/api";
/**
* A fake function that returns an empty object casted to type T
* @returns Empty object as type T
*/
export function Type<T>(): T {
return {} as T;
}

export interface JwtTokenObtainPair {
access: string;
refresh: string;
}

export const USER_TYPE_OPTIONS = [
{ id: "Pharmacist", role: "Pharmacist", readOnly: false },
{ id: "Volunteer", role: "Volunteer", readOnly: false },
{ id: "StaffReadOnly", role: "Staff", readOnly: true },
{ id: "Staff", role: "Staff", readOnly: false },
// { id: "NurseReadOnly", role: "Nurse", readOnly: true },
{ id: "Nurse", role: "Nurse", readOnly: false },
{ id: "Doctor", role: "Doctor", readOnly: false },
{ id: "WardAdmin", role: "Ward Admin", readOnly: false },
{ id: "LocalBodyAdmin", role: "Local Body Admin", readOnly: false },
{ id: "DistrictLabAdmin", role: "District Lab Admin", readOnly: false },
{ id: "DistrictReadOnlyAdmin", role: "District Admin", readOnly: true },
{ id: "DistrictAdmin", role: "District Admin", readOnly: false },
{ id: "StateLabAdmin", role: "State Lab Admin", readOnly: false },
{ id: "StateReadOnlyAdmin", role: "State Admin", readOnly: true },
{ id: "StateAdmin", role: "State Admin", readOnly: false },
] as const;

export type UserRole = (typeof USER_TYPE_OPTIONS)[number]["id"];

export type UserBareMinimum = {
id: number;
username: string;
first_name: string;
last_name: string;
email: string;
user_type: UserRole;
last_login: string | undefined;
read_profile_picture_url?: string;
};

export interface LiveKitTokenRequest {
source: string;
Expand All @@ -12,6 +54,12 @@ export interface LiveKitTokenResponse {
}

const routes = {
// User Endpoints
currentUser: {
path: "/api/v1/users/getcurrentuser/",
TRes: Type<UserBareMinimum>(),
},
// Livekit Endpoints
livekit: {
create_room: {
path: "/api/care_livekit/create_room/",
Expand Down
104 changes: 104 additions & 0 deletions src/api/request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import careConfig from "@careConfig";

import handleResponse from "@/api/handleResponse";
import { RequestOptions, RequestResult, Route } from "@/api/types";
import { makeHeaders, makeUrl } from "@/api/utils";

type ControllerXORControllerRef =
| {
controller?: AbortController;
controllerRef?: undefined;
}
| {
controller?: undefined;
controllerRef: React.MutableRefObject<AbortController | undefined>;
};

type Options<TData, TBody> = RequestOptions<TData, TBody> &
ControllerXORControllerRef;

export default async function request<TData, TBody>(
{ path, method, noAuth }: Route<TData, TBody>,
{
query,
body,
pathParams,
controller,
controllerRef,
onResponse,
silent,
reattempts = 3,
}: Options<TData, TBody> = {}
): Promise<RequestResult<TData>> {
if (controllerRef) {
controllerRef.current?.abort();
controllerRef.current = new AbortController();
}

const signal = controller?.signal ?? controllerRef?.current?.signal;
const url = `${careConfig.apiUrl}${makeUrl(path, query, pathParams)}`;

const options: RequestInit = { method, signal };

if (body) {
options.body = JSON.stringify(body);
}

let result: RequestResult<TData> = {
res: undefined,
data: undefined,
error: undefined,
};

for (let i = 0; i < reattempts + 1; i++) {
options.headers = makeHeaders(noAuth ?? false);

try {
const res = await fetch(url, options);

const data = await getResponseBody<TData>(res);

result = {
res,
data: res.ok ? data : undefined,
error: res.ok ? undefined : (data as Record<string, unknown>),
};

onResponse?.(result);
handleResponse(result, silent);

return result;
} catch (error: any) {
result = { error, res: undefined, data: undefined };
}
}

console.error(
`Request failed after ${reattempts + 1} attempts`,
result.error
);
return result;
}

async function getResponseBody<TData>(res: Response): Promise<TData> {
if (!(res.headers.get("content-length") !== "0")) {
return null as TData;
}

const isJson = res.headers.get("content-type")?.includes("application/json");
const isImage = res.headers.get("content-type")?.includes("image");

if (isImage) {
return (await res.blob()) as TData;
}

if (!isJson) {
return (await res.text()) as TData;
}

try {
return await res.json();
} catch {
return (await res.text()) as TData;
}
}
Loading

0 comments on commit 3038582

Please sign in to comment.