Skip to content

Commit

Permalink
[Frontend] monorepo changes (#2084)
Browse files Browse the repository at this point in the history
chore: Monorepo updates and enhancements
- Updated `404` component with default values
- Updated package dependencies
- feat: Added page links' variables and updated details pages
- Refactored document page structure
- fix: Resolved build error due to duplicate import
- Switched package manager from `npm` to `pnpm`
- Updated Dockerfile configuration for Tenscan frontend
- fix: Display full address on desktop view
- Added monorepo structure documentation for TEN frontend projects
  • Loading branch information
Jennievon authored Oct 8, 2024
1 parent ca2a23d commit d88f553
Show file tree
Hide file tree
Showing 124 changed files with 32,540 additions and 0 deletions.
25 changes: 25 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "go-ten",
"version": "1.0.0",
"description": "go-ten",
"main": "index.js",
"directories": {
"lib": "lib"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ten-protocol/go-ten.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/ten-protocol/go-ten/issues"
},
"homepage": "https://github.com/ten-protocol/go-ten#readme",
"dependencies": {
"turbo": "^1.13.3"
}
}
72 changes: 72 additions & 0 deletions packages/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Monorepo Structure for TEN Frontend Projects

This repository uses a monorepo setup to manage multiple frontend projects and shared resources such as components, utilities, and hooks. The structure enhances code reuse, maintainability, and collaboration across projects like **Tenscan**, **Gateway**, **Bridge**, and more.

## Folder Structure

```bash
📁 packages
├── 📁 apis - Server-side logic, API routes, and backend services
│ ├── 📁 .config - Configuration files for the APIs
│ ├── 📁 src - Source files for API logic
│ └── 📁 storage - Storage-related logic or utilities
├── 📁 eslint-config - Centralized ESLint configuration for all frontend projects
├── 📁 shared - Reusable components, hooks, and utilities shared across frontend apps
│ └── 📁 src - Main directory containing shared code
├── 📁 typescript-config - Centralized TypeScript configurations
│ ├── 📄 base.json - Base TypeScript configuration for general projects
│ ├── 📄 nextjs.json - Configuration specific to Next.js projects
│ └── 📄 react-library.json - Configuration for React libraries
└── 📁 ui -
├── 📁 api - API logic consumed by the frontend
├── 📁 components - Reusable React components used in the UI
├── 📁 hooks - Custom hooks used across the frontend
├── 📁 lib - Utility functions used across the frontend
├── 📁 public - Static files such as images and assets
├── 📁 services - External service interactions like APIs
├── 📁 routes - Routing configuration and route-related logic
└── 📁 stores - Global state mgt
```

## Getting Started

1. **Clone the Repository:**

```bash
git clone https://github.com/ten-protocol/go-ten.git
```

2. **Install Dependencies:**

```bash
pnpm install
```

3. **Navigate to the Project:**

```bash
Tenscan: cd tools/tenscan/frontend
Gateway: cd tools/walletextension/frontend
Bridge: cd tools/bridge-frontend
```

4. **Run the Project:**

```bash
pnpm dev
```


## Built With

- [Next.js](https://nextjs.org/)
- [Tailwind CSS](https://tailwindcss.com/)
- [TypeScript](https://www.typescriptlang.org/)

## Contributing

Contributions are welcome! Follow our [contribution guidelines](/docs/_docs/community/contributions.md).

## License

This project is licensed under the [GNU Affero General Public License v3.0](/LICENSE).
28 changes: 28 additions & 0 deletions packages/apis/.config/openapi-codegen.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// @ts-expect-error
import { defineConfig } from "@openapi-codegen/cli";
import {
generateReactQueryComponents,
generateSchemaTypes,
} from "@openapi-codegen/typescript";

export default defineConfig({
digest: {
from: {
relativePath: "./storage/digest-openapi.json",
source: "file",
},
outputDir: "./src/api/codegen",
// @ts-expect-error
to: async (context) => {
const filenamePrefix = "digest";

const { schemasFiles } = await generateSchemaTypes(context, {
filenamePrefix,
});
return await generateReactQueryComponents(context, {
filenamePrefix,
schemasFiles,
});
},
},
});
4 changes: 4 additions & 0 deletions packages/apis/.config/p2o.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"outputFormat": "json",
"operationId": "auto"
}
28 changes: 28 additions & 0 deletions packages/apis/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@repo/apis",
"version": "0.0.0",
"private": true,
"scripts": {
"sync": "npm run api:generate",
"api:generate": "pnpm run convert:collection && npm run generate:fetcher",
"generate:fetcher": "npx openapi-codegen -c .config/openapi-codegen.config.ts digest",
"convert:collection": "pnpm exec p2o ./storage/digest-collection.json -f ./storage/digest-openapi.json -o .config/p2o.config.json"
},
"dependencies": {
"@repo/shared": "workspace:*",
"@repo/typescript-config": "workspace:*",
"destr": "^2.0.2",
"effect": "^2.4.1",
"ofetch": "1.3.3",
"ramda": "^0.29.1",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@openapi-codegen/cli": "^2.0.0",
"@openapi-codegen/typescript": "^8.0.0",
"@types/ramda": "^0.29.10",
"postman-to-openapi": "^3.0.1",
"typescript": "5.3.3",
"vite": "^5.0.11"
}
}
166 changes: 166 additions & 0 deletions packages/apis/src/api/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { safeStr } from "@repo/shared/src/data.helpers";
import { Match } from "effect";
import { FetchError } from "ofetch";
import { has, identity, is } from "ramda";
import {
ApiError,
ApiErrors,
Okay,
ResponseType,
SafeRes,
UnknownError,
} from "./response-types";

export const resolveUrl = (
url: string,
queryParams: Record<string, string> = {},
pathParams: Record<string, string> = {},
) => {
let query = new URLSearchParams(queryParams).toString();
if (query) query = `?${query}`;

return url.replace(/\{\w*\}/g, (key) => pathParams[key.slice(1, -1)]) + query;
};

export async function safeFetchResponse<T>(
promise: Promise<T>,
): Promise<SafeRes<T>> {
try {
const value = await promise;
return resolveFetchResponse<T>(value);
} catch (err: unknown) {
return resolveFetchError(err);
}
}

export const resolveFetchResponse = <T>(response: T): SafeRes<T> => {
const hasError = has("error", response) && response.error === true;
const hasMessage = has("msg", response);

const messageIsStr = hasMessage && typeof response?.msg === "string";

if (UnacceptedError.validate(response)) {
return UnacceptedError.respond(response);
}

if (Array.isArray(response)) return Okay(response);

if (hasError && hasMessage && is(Object, response?.msg))
// @ts-expect-error Still trying to response structure
return ValidationError(response?.msg);

if (hasError && hasMessage && messageIsStr)
return ApiError({
message: response.msg as string,

error: response.error,
});

if (response === undefined || response === null) {
return UnknownError({
message: "Something went wrong",
value: response,
});
}

return Okay(response);
};

// biome-ignore lint/suspicious/noExplicitAny: Error type must be any
export function resolveFetchError(error: any): ApiErrors {
if (
["Network Error", "NetworkError"].some((str) =>
safeStr(error?.message).includes(str),
)
) {
return ApiError({
message: "Seems like you're offline. Please check your network",
});
}

return handleErrorByType(error);
}

const handleErrorByType = ResponseType.pipe(
Match.tag("ApiError", identity),
Match.tag("ValidationError", identity),
Match.tag("UnknownError", identity),
Match.orElse((err) => handleRandomError(err)),
);

function handleRandomError(error: unknown) {
const err = error as FetchError;
if (err instanceof FetchError) {
const { status = 400 } = guessRequestError(err);
const reason_for_failure = err?.data?.message || err.message;

if (status === 404)
return ApiError({ message: "404: Resource not found", error: err });

if (401 === status) {
return ApiError({
message: "Unable to process request. You're unauthorized",
error: err,
});
}

if (403 === status) {
return ApiError({
message: "You do not have permission to perform this operation",
error: err,
});
}

if (status >= 400 && status < 500)
return ApiError({ message: reason_for_failure, error: err });

if (status >= 500)
return ApiError({ message: reason_for_failure, error: err });
}

const err_msg = has("message", error) ? safeStr(error.message) : "";

if (err_msg.includes("<no response>")) {
return ApiError({
message: "Something new wrong. Server didn't respond",
});
}

return ApiError({ message: err_msg });

function guessRequestError(err: FetchError) {
return err instanceof FetchError
? err
: { status: 0, statusText: "UNKNOWN" };
}
}

export type ResponseResolver = {
validate: (data: unknown) => boolean;
respond: <T>(data: T) => ApiErrors;
interceptResponse: <T>(data: T) => T;
};

export const UnacceptedError: ResponseResolver = {
validate(response: unknown) {
return (
has("accepted", response) &&
has("message", response) &&
response.accepted === false
);
},

respond(response: unknown) {
return ApiError({
// @ts-expect-error
message: response?.message ?? "Unknown Error...",
error: response,
});
},

interceptResponse(response) {
if (!this.validate(response)) return response;

throw this.respond(response);
},
};
Loading

0 comments on commit d88f553

Please sign in to comment.