Skip to content

Commit 9c2b147

Browse files
authored
Merge pull request #14 from rayonstudios/v-1.4
V 2.1
2 parents 1a98e7a + 334f05f commit 9c2b147

70 files changed

Lines changed: 4264 additions & 266 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.development

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
VITE_API_BASE_URL=http://localhost:8000/dev/api
2-
VITE_ENV=dev
1+
VITE_API_BASE_URL=https://be.starters.rayonstudios.com/api/v1
2+
VITE_ENV=dev
3+
VITE_HCAPTCHA_SITE_KEY=cd86c190-7a30-4042-9468-018cd91ef63c

.env.production

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
VITE_API_BASE_URL=http://localhost:8000/api
2-
VITE_ENV=production
1+
VITE_API_BASE_URL=https://be.starters.rayonstudios.com/api
2+
VITE_ENV=production
3+
VITE_HCAPTCHA_SITE_KEY=cd86c190-7a30-4042-9468-018cd91ef63c

.eslintrc.cjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,10 @@ module.exports = {
1515
"warn",
1616
{ allowConstantExport: true },
1717
],
18+
"@typescript-eslint/no-explicit-any": "off",
19+
"@typescript-eslint/ban-types": "off",
20+
"@typescript-eslint/ban-ts-comment": "off",
21+
"no-useless-escape": "off",
22+
"no-empty": "off",
1823
},
1924
};

README.md

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ Rayon React Starter is an opinionated starter kit designed to scaffold React pro
1212
- **Redux Toolkit**: State management with slices and thunks
1313
- **Ant Design**: Elegant and consistent UI components
1414
- **Tailwind CSS**: Utility-first CSS framework for rapid UI development
15-
- **Axios**: Promise-based HTTP client for making requests
15+
- **OpenAPI Fetch**: Type-safe API client generated from OpenAPI schemas
16+
- **Axios**: Promise-based HTTP client for making requests (used as the fetch implementation for OpenAPI Fetch)
1617

1718
## Features
1819

@@ -29,6 +30,8 @@ Rayon React Starter is an opinionated starter kit designed to scaffold React pro
2930
- 🛠 **Built-in Utilities**: Handy utility functions, hooks, and components to cover common use cases
3031
- 🧹 **Prettier and ESLint Config**: Enforce code style and quality with Prettier and ESLint configurations
3132
- 🚀 **CI/CD with Firebase Hosting**: Continuous Integration and Deployment setup using Firebase Hosting and Github Actions for seamless deployment
33+
- 📊 **ServerPaginatedTable**: Automatic server-side pagination, filtering, and sorting for data tables
34+
- 📝 **OpenAPI Type Generation**: Automatic type generation from OpenAPI schemas for type-safe API calls
3235

3336
## Getting Started
3437

@@ -44,7 +47,7 @@ Ensure you have the following installed:
4447
1. Clone the repository:
4548
```bash
4649
git clone https://github.com/rayonstudios/rayon_react_starter
47-
cd rayon-gcp-starter
50+
cd rayon_react_starter
4851
```
4952
2. Install dependencies:
5053
```bash
@@ -57,26 +60,102 @@ Ensure you have the following installed:
5760

5861
The application should now be running on http://localhost:5173
5962

60-
## Folde Structure
63+
## Environment Configuration
64+
65+
The project supports different environments:
66+
67+
```bash
68+
# Development mode
69+
yarn dev # Run with development configuration
70+
yarn build:dev # Build with development configuration
71+
72+
# Production mode
73+
yarn prod # Run with production configuration
74+
yarn build:prod # Build with production configuration
75+
```
76+
77+
## API Integration
78+
79+
### OpenAPI Type Generation
80+
81+
The project includes a script to generate TypeScript types from an OpenAPI schema:
82+
83+
```bash
84+
yarn gen-api-types:dev # Generate types from development API
85+
yarn gen-api-types:prod # Generate types from production API
86+
```
87+
88+
The script fetches the OpenAPI schema from the API endpoint (configured via `VITE_API_BASE_URL` in the environment files) and generates TypeScript types in `src/lib/types/openapi-fetch.d.ts`.
89+
90+
### Making API Calls
91+
92+
The project uses `openapi-fetch` with Axios as the fetch implementation for type-safe API calls:
93+
94+
```typescript
95+
import apiClient, { withApiResponseHandling } from '@/lib/openapi-fetch.config';
96+
97+
// Example API call with type safety
98+
const { data } = await withApiResponseHandling(
99+
apiClient.GET('/api/users/{id}', {
100+
params: { path: { id: userId } }
101+
})
102+
);
103+
```
104+
105+
## Folder Structure
61106
62107
```bash
63108
├── lib/ # Reusable, feature-independent code
109+
│ ├── components/ # Shared UI components
110+
│ ├── hooks/ # Custom React hooks
111+
│ ├── types/ # TypeScript type definitions
112+
│ └── utils/ # Utility functions
64113
├── modules/ # Feature-dependent code
114+
│ ├── auth/ # Authentication related components and logic
115+
│ └── ... # Other feature modules
65116
├── pages/ # Page components
66-
└── ...
117+
└── scripts/ # Build and utility scripts
67118
```
68119
69120
- Use **snake_case** for file and folder names to maintain consistency.
70121
- Feature-based folder structure ensures a clean separation of concerns for better scalability and maintainability.
71122
123+
## Common Components
124+
125+
### ServerPaginatedTable
126+
127+
`ServerPaginatedTable` is a powerful component for handling server-side paginated data:
128+
129+
```typescript
130+
import { ServerPaginatedTable } from '@/lib/components/table/server_paginated_table';
131+
132+
// Example usage
133+
<ServerPaginatedTable
134+
url="/api/users"
135+
columns={columns}
136+
queryParams={{ status: 'active' }}
137+
/>
138+
```
139+
140+
The component automatically handles:
141+
- Pagination
142+
- Sorting
143+
- Filtering
144+
- Loading states
145+
- Error handling
146+
72147
## Best Practices
73148
74149
- Reusable code should generally reside in the `lib` folder.
75150
- Feature-dependent code should be organized under `modules`.
76-
Pages components should be placed under the `pages` folder.
151+
- Pages components should be placed under the `pages` folder.
152+
- Use TypeScript for type safety across the application.
153+
- Follow the established patterns for state management with Redux Toolkit.
154+
- Use OpenAPI Fetch for API calls to ensure type safety.
155+
- Use ServerPaginatedTable for displaying data from API endpoints with pagination.
77156
78157
## Roadmap
79158
80159
- [ ] Detailed Developer Documentation
81160
- [ ] Add unit, integration and e2e tests support
82-
- [ ] Convert to a modular CLI based tools
161+
- [ ] Convert to a modular CLI based tool

index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!DOCTYPE html>
1+
<!doctype html>
22
<html lang="en">
33
<head>
44
<meta charset="UTF-8" />
@@ -15,7 +15,7 @@
1515
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
1616
<meta
1717
name="description"
18-
content="Rayon React Starter is a starter template (batteries included) for React applications."
18+
content="Rayon React Starter is an opinionated starter kit designed to scaffold React projects quickly with a comprehensive and well-structured environment. Built with a modern tech stack and batteries included, it helps you start building your project in no time."
1919
/>
2020
<title>Rayon React Starter</title>
2121
</head>

package.json

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
{
22
"name": "rayon_react_starter",
33
"private": true,
4-
"version": "1.4",
4+
"version": "2.1",
55
"description": "Rayon React Starter is an opinionated starter kit designed to scaffold React projects quickly with a comprehensive and well-structured environment. Built with a modern tech stack and batteries included, it helps you start building your project in no time.",
66
"type": "module",
77
"scripts": {
8-
"dev": "vite",
9-
"prod": "vite --mode production",
10-
"build:prod": "tsc && vite build",
11-
"build:dev": "tsc && vite build --mode development",
8+
"dev": "npm run gen-types:dev && vite",
9+
"prod": "npm run gen-types:prod && vite --mode production",
10+
"build:prod": "npm run gen-types:prod && tsc && vite build",
11+
"build:dev": "npm run gen-types:dev && tsc && vite build --mode development",
12+
"gen-types:dev": "tsx scripts/gen-openapi-types.ts development",
13+
"gen-types:prod": "tsx scripts/gen-openapi-types.ts production",
1214
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
1315
"preview": "vite preview",
1416
"watch:types": "tsc -w",
@@ -18,6 +20,7 @@
1820
"@ahooksjs/use-url-state": "^3.5.1",
1921
"@ant-design/colors": "^7.0.2",
2022
"@ant-design/icons": "^5.2.6",
23+
"@hcaptcha/react-hcaptcha": "^1.12.0",
2124
"@reduxjs/toolkit": "^2.1.0",
2225
"@types/lodash": "^4.17.0",
2326
"@types/qs": "^6.9.16",
@@ -29,6 +32,7 @@
2932
"dayjs": "^1.11.10",
3033
"i18next": "^23.10.1",
3134
"lucide": "^0.447.0",
35+
"openapi-fetch": "^0.13.5",
3236
"qs": "^6.13.0",
3337
"react": "^18.2.0",
3438
"react-dom": "^18.2.0",
@@ -41,6 +45,7 @@
4145
},
4246
"devDependencies": {
4347
"@types/color": "^3.0.6",
48+
"@types/node": "^22.14.0",
4449
"@types/react": "^18.2.43",
4550
"@types/react-dom": "^18.2.17",
4651
"@typescript-eslint/eslint-plugin": "^6.14.0",
@@ -53,10 +58,12 @@
5358
"eslint-plugin-react-refresh": "^0.4.5",
5459
"husky": ">=6",
5560
"lint-staged": ">=10",
61+
"openapi-typescript": "^7.6.1",
5662
"postcss": "^8.4.33",
5763
"prettier": "^3.3.3",
5864
"tailwindcss": "^3.4.1",
59-
"typescript": "^5.2.2",
65+
"tsx": "^4.19.3",
66+
"typescript": "^5.8.3",
6067
"vite": "^5.0.8",
6168
"vite-tsconfig-paths": "^4.3.2"
6269
},

scripts/gen-openapi-types.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
#!/usr/bin/env tsx
2+
import * as fs from "fs";
3+
import * as http from "http";
4+
import * as https from "https";
5+
import openapiTS, { astToString } from "openapi-typescript";
6+
import * as path from "path";
7+
import ts from "typescript";
8+
import { URL } from "url";
9+
10+
const DATE = ts.factory.createTypeReferenceNode(
11+
ts.factory.createIdentifier("Date")
12+
); // `Date`
13+
const FILE = ts.factory.createTypeReferenceNode(
14+
ts.factory.createIdentifier("File")
15+
); // `Blob
16+
const NULL = ts.factory.createLiteralTypeNode(ts.factory.createNull()); // `null`
17+
18+
// Get environment type from command line arguments
19+
const envType = process.argv[2];
20+
21+
if (!envType) {
22+
console.error(
23+
"Usage: tsx gen-openapi-types.ts <environment_type> (development|production)"
24+
);
25+
process.exit(1);
26+
}
27+
28+
const envFile = path.join(process.cwd(), `.env.${envType}`);
29+
30+
// Check if the environment file exists
31+
if (!fs.existsSync(envFile)) {
32+
console.error(`Error: Environment file .env.${envType} does not exist`);
33+
process.exit(1);
34+
}
35+
36+
// Read the environment file
37+
const envContent = fs.readFileSync(envFile, "utf8");
38+
39+
// Extract API base URL from environment file
40+
const apiBaseUrlMatch = envContent.match(/VITE_API_BASE_URL=(.+)/);
41+
const apiBaseUrl = apiBaseUrlMatch ? apiBaseUrlMatch[1].trim() : null;
42+
43+
if (!apiBaseUrl) {
44+
console.error(`Error: VITE_API_BASE_URL not found in .env.${envType}`);
45+
process.exit(1);
46+
}
47+
48+
console.log(`Using API base URL: ${apiBaseUrl}`);
49+
50+
// Fetch OpenAPI schema
51+
const openApiUrl = `${apiBaseUrl}/openapi.json`;
52+
console.log(`Fetching OpenAPI schema from: ${openApiUrl}`);
53+
54+
const tempFilePath = path.join(process.cwd(), "openapi.json");
55+
const outputDir = path.join(process.cwd(), "src", "lib", "types");
56+
const outputPath = path.join(outputDir, "openapi-fetch.d.ts");
57+
58+
// Create output directory if it doesn't exist
59+
if (!fs.existsSync(outputDir)) {
60+
fs.mkdirSync(outputDir, { recursive: true });
61+
}
62+
63+
// Function to download the OpenAPI schema
64+
function downloadSchema(): Promise<void> {
65+
return new Promise((resolve, reject) => {
66+
const urlObj = new URL(openApiUrl);
67+
const client = urlObj.protocol === "https:" ? https : http;
68+
69+
const req = client.get(openApiUrl, (res) => {
70+
if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) {
71+
reject(new Error(`Failed to fetch OpenAPI schema: ${res.statusCode}`));
72+
return;
73+
}
74+
75+
const fileStream = fs.createWriteStream(tempFilePath);
76+
res.pipe(fileStream);
77+
78+
fileStream.on("finish", () => {
79+
fileStream.close();
80+
resolve();
81+
});
82+
});
83+
84+
req.on("error", (error) => {
85+
reject(error);
86+
});
87+
});
88+
}
89+
90+
// Main execution
91+
async function main(): Promise<void> {
92+
try {
93+
// Download schema
94+
await downloadSchema();
95+
96+
// Generate TypeScript types
97+
console.log("Generating TypeScript types");
98+
const openApiSchema = fs.readFileSync(tempFilePath, "utf8");
99+
const ast = await openapiTS(openApiSchema, {
100+
transform(schemaObject) {
101+
// handle date-time type
102+
if (schemaObject.format === "date-time") {
103+
return {
104+
schema: schemaObject.nullable
105+
? ts.factory.createUnionTypeNode([DATE, NULL])
106+
: DATE,
107+
questionToken: true,
108+
};
109+
}
110+
111+
// handle File type
112+
if (schemaObject.format === "binary") {
113+
return {
114+
schema: schemaObject.nullable
115+
? ts.factory.createUnionTypeNode([FILE, NULL])
116+
: FILE,
117+
questionToken: true,
118+
};
119+
}
120+
},
121+
});
122+
const contents = astToString(ast);
123+
fs.writeFileSync(outputPath, contents);
124+
125+
// Clean up
126+
fs.unlinkSync(tempFilePath);
127+
128+
console.log(
129+
`TypeScript types successfully generated at src/lib/types/openapi-fetch.d.ts`
130+
);
131+
} catch (error) {
132+
console.error(error instanceof Error ? error.message : String(error));
133+
134+
// Clean up temp file if it exists
135+
if (fs.existsSync(tempFilePath)) {
136+
fs.unlinkSync(tempFilePath);
137+
}
138+
139+
process.exit(1);
140+
}
141+
}
142+
143+
main();

0 commit comments

Comments
 (0)