create-tririga-react-ts-vite-app 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/index.js +138 -0
- package/package.json +22 -0
- package/template/.env.example +14 -0
- package/template/README.md +73 -0
- package/template/_gitignore +26 -0
- package/template/components.json +23 -0
- package/template/index.html +13 -0
- package/template/package.json +45 -0
- package/template/public/tri-app-config.json +9 -0
- package/template/public/vite.svg +1 -0
- package/template/src/App.tsx +15 -0
- package/template/src/components/MainLayout.tsx +64 -0
- package/template/src/components/index.ts +1 -0
- package/template/src/components/ui/accordion.tsx +64 -0
- package/template/src/components/ui/avatar.tsx +109 -0
- package/template/src/components/ui/badge.tsx +48 -0
- package/template/src/components/ui/button.tsx +64 -0
- package/template/src/components/ui/calendar.tsx +222 -0
- package/template/src/components/ui/card.tsx +92 -0
- package/template/src/components/ui/dialog.tsx +158 -0
- package/template/src/components/ui/input.tsx +21 -0
- package/template/src/components/ui/label.tsx +22 -0
- package/template/src/components/ui/popover.tsx +89 -0
- package/template/src/components/ui/select.tsx +188 -0
- package/template/src/components/ui/separator.tsx +28 -0
- package/template/src/components/ui/sonner.tsx +38 -0
- package/template/src/components/ui/switch.tsx +35 -0
- package/template/src/components/ui/tabs.tsx +89 -0
- package/template/src/components/ui/textarea.tsx +18 -0
- package/template/src/components/ui/tooltip.tsx +55 -0
- package/template/src/index.css +38 -0
- package/template/src/lib/utils.ts +6 -0
- package/template/src/main.tsx +68 -0
- package/template/src/model/AppModel.ts +14 -0
- package/template/src/pages/HomePage.tsx +15 -0
- package/template/src/pages/index.ts +1 -0
- package/template/src/services/AuthService.ts +58 -0
- package/template/src/services/tririgaService.ts +42 -0
- package/template/src/types/tririga-react-components.d.ts +107 -0
- package/template/tsconfig.json +25 -0
- package/template/vite.config.ts +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# create-pb-tririga-react-app
|
|
2
|
+
|
|
3
|
+
A CLI tool to scaffold modern TRIRIGA UX applications using React, TypeScript, and Vite.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
You don't need to install this package locally. You can run it directly using `npx` and replace `my-tririga-app` with your desired app name:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx create-pb-tririga-react-app my-tririga-app
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Framework**: React 18 + TypeScript
|
|
16
|
+
- **Build Tool**: Vite (Fast HMR & Bundling)
|
|
17
|
+
- **UI Library**: ShadCN
|
|
18
|
+
- **Routing**: HashRouter (Compatible with TRIRIGA context paths)
|
|
19
|
+
- **Deployment**: Ready-to-use scripts for `tri-deploy`
|
|
20
|
+
|
|
21
|
+
## Getting Started with your new app
|
|
22
|
+
|
|
23
|
+
Once created:
|
|
24
|
+
|
|
25
|
+
1. `cd my-tririga-app`
|
|
26
|
+
2. Install pnpm `npm install -g pnpm`
|
|
27
|
+
3. Install dependencies `pnpm install`
|
|
28
|
+
4. Make your first deployment with `pnpm run build:deploy`
|
package/index.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const readline = require('readline');
|
|
6
|
+
|
|
7
|
+
// 1. Get project name from command line
|
|
8
|
+
const projectName = process.argv[2] || 'my-tririga-app';
|
|
9
|
+
const currentDir = process.cwd();
|
|
10
|
+
const projectDir = path.join(currentDir, projectName);
|
|
11
|
+
|
|
12
|
+
// 2. Create Directory
|
|
13
|
+
if (fs.existsSync(projectDir)) {
|
|
14
|
+
console.error(`Directory ${projectName} already exists.`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
fs.mkdirSync(projectDir);
|
|
18
|
+
|
|
19
|
+
// 3. Copy Template Files
|
|
20
|
+
const templateDir = path.join(__dirname, 'template');
|
|
21
|
+
|
|
22
|
+
function copyRecursiveSync(src, dest) {
|
|
23
|
+
const exists = fs.existsSync(src);
|
|
24
|
+
const stats = exists && fs.statSync(src);
|
|
25
|
+
const isDirectory = exists && stats.isDirectory();
|
|
26
|
+
|
|
27
|
+
if (isDirectory) {
|
|
28
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
29
|
+
fs.readdirSync(src).forEach(childItemName => {
|
|
30
|
+
copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName));
|
|
31
|
+
});
|
|
32
|
+
} else {
|
|
33
|
+
fs.copyFileSync(src, dest);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
copyRecursiveSync(templateDir, projectDir);
|
|
38
|
+
|
|
39
|
+
// Rename _gitignore to .gitignore
|
|
40
|
+
const gitignorePath = path.join(projectDir, '_gitignore');
|
|
41
|
+
if (fs.existsSync(gitignorePath)) {
|
|
42
|
+
fs.renameSync(gitignorePath, path.join(projectDir, '.gitignore'));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Helper function for user prompts
|
|
46
|
+
function askQuestion(query, hidden = false) {
|
|
47
|
+
const rl = readline.createInterface({
|
|
48
|
+
input: process.stdin,
|
|
49
|
+
output: process.stdout,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (hidden) {
|
|
53
|
+
let stdinListener = (char) => {
|
|
54
|
+
char = char + '';
|
|
55
|
+
switch (char) {
|
|
56
|
+
case '\n':
|
|
57
|
+
case '\r':
|
|
58
|
+
case '\u0004':
|
|
59
|
+
process.stdin.removeListener('data', stdinListener);
|
|
60
|
+
break;
|
|
61
|
+
default:
|
|
62
|
+
process.stdout.write('\x1B[2K\x1B[200D' + query + Array(rl.line.length + 1).join('*'));
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
process.stdin.on('data', stdinListener);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return new Promise((resolve) => rl.question(query, (ans) => {
|
|
70
|
+
rl.close();
|
|
71
|
+
if (hidden) console.log(); // Add newline after hidden input
|
|
72
|
+
resolve(ans);
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function configureApp() {
|
|
77
|
+
console.log('\n--- Configuration Setup ---\n');
|
|
78
|
+
|
|
79
|
+
const triUrl = await askQuestion('TRIRIGA Environment URL (e.g., https://your-tririga-server.com): ');
|
|
80
|
+
const triUser = await askQuestion('TRIRIGA Username: ');
|
|
81
|
+
const triPassword = await askQuestion('TRIRIGA Password: ', true);
|
|
82
|
+
const appTitle = await askQuestion('App Title (e.g., TRIRIGA Service Request): ');
|
|
83
|
+
const appExposedName = await askQuestion('Application Exposed Name (e.g., myTririgaApp): ');
|
|
84
|
+
const modelAndView = await askQuestion('Model and View Name (e.g., myTririgaApp): ');
|
|
85
|
+
const webViewMetadataName = await askQuestion(`Web View Metadata Exposed Name (default: ${projectName}): `);
|
|
86
|
+
|
|
87
|
+
const finalWebViewName = webViewMetadataName || projectName;
|
|
88
|
+
|
|
89
|
+
// 4. Update package.json name with Web View Metadata Exposed Name
|
|
90
|
+
const pkgJsonPath = path.join(projectDir, 'package.json');
|
|
91
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
|
|
92
|
+
pkgJson.name = finalWebViewName;
|
|
93
|
+
fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
|
|
94
|
+
|
|
95
|
+
// 5. Update .env file
|
|
96
|
+
const envExamplePath = path.join(projectDir, '.env.example');
|
|
97
|
+
const envPath = path.join(projectDir, '.env');
|
|
98
|
+
if (fs.existsSync(envExamplePath)) {
|
|
99
|
+
let envContent = fs.readFileSync(envExamplePath, 'utf8');
|
|
100
|
+
envContent = envContent.replace(/TRI_URL=.*/g, `TRI_URL=${triUrl}`);
|
|
101
|
+
envContent = envContent.replace(/TRI_USER=.*/g, `TRI_USER=${triUser}`);
|
|
102
|
+
envContent = envContent.replace(/TRI_PASSWORD=.*/g, `TRI_PASSWORD=${triPassword}`);
|
|
103
|
+
fs.writeFileSync(envPath, envContent);
|
|
104
|
+
// Delete .env.example
|
|
105
|
+
fs.unlinkSync(envExamplePath);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 6. Update tririgaService.ts
|
|
109
|
+
const tririgaServicePath = path.join(projectDir, 'src', 'services', 'tririgaService.ts');
|
|
110
|
+
if (fs.existsSync(tririgaServicePath)) {
|
|
111
|
+
let tsContent = fs.readFileSync(tririgaServicePath, 'utf8');
|
|
112
|
+
tsContent = tsContent.replace(/modelAndView:\s*"[^"]*"/, `modelAndView: "${modelAndView}"`);
|
|
113
|
+
tsContent = tsContent.replace(/appExposedName:\s*"[^"]*"/, `appExposedName: "${appExposedName}"`);
|
|
114
|
+
fs.writeFileSync(tririgaServicePath, tsContent);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 7. Update vite.config.ts base path
|
|
118
|
+
const viteConfigPath = path.join(projectDir, 'vite.config.ts');
|
|
119
|
+
if (fs.existsSync(viteConfigPath)) {
|
|
120
|
+
let viteContent = fs.readFileSync(viteConfigPath, 'utf8');
|
|
121
|
+
// Regex matches the whole base line and replaces it, handling single or double quotes
|
|
122
|
+
viteContent = viteContent.replace(/base:\s*['"]\/app\/[^/]+\/['"],/, `base: '/app/${appExposedName}/',`);
|
|
123
|
+
fs.writeFileSync(viteConfigPath, viteContent);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 8. Update index.html Title
|
|
127
|
+
const indexHtmlPath = path.join(projectDir, 'index.html');
|
|
128
|
+
if (fs.existsSync(indexHtmlPath) && appTitle) {
|
|
129
|
+
let htmlContent = fs.readFileSync(indexHtmlPath, 'utf8');
|
|
130
|
+
htmlContent = htmlContent.replace(/<title>.*<\/title>/, `<title>${appTitle}</title>`);
|
|
131
|
+
fs.writeFileSync(indexHtmlPath, htmlContent);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.log(`\nSuccess! Created and configured ${projectName}.`);
|
|
135
|
+
console.log(`\nTo get started:\n cd ${projectName}\n npm install -g pnpm\n pnpm install\n pnpm run build:deploy\n`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
configureApp();
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-tririga-react-ts-vite-app",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Scaffold a TRIRIGA React app with Vite and TypeScript",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-tririga-react-ts-vite-app": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.js",
|
|
11
|
+
"template",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"tririga",
|
|
16
|
+
"react",
|
|
17
|
+
"vite",
|
|
18
|
+
"scaffold"
|
|
19
|
+
],
|
|
20
|
+
"author": "Noah Richard",
|
|
21
|
+
"license": "ISC"
|
|
22
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Local Development Config
|
|
2
|
+
VITE_TRIRIGA_URL=http://localhost:8001
|
|
3
|
+
VITE_INSTANCE_ID=-1
|
|
4
|
+
VITE_CONTEXT_PATH=/app
|
|
5
|
+
VITE_MODEL_AND_VIEW=devView
|
|
6
|
+
VITE_BASE_PATH=/app/devView
|
|
7
|
+
VITE_EXPOSED_NAME=devView
|
|
8
|
+
VITE_SSO=false
|
|
9
|
+
VITE_AUTO_LOGIN=true
|
|
10
|
+
|
|
11
|
+
# Deployment Config (used by tri-deploy)
|
|
12
|
+
TRI_URL=https://your-tririga-server.com
|
|
13
|
+
TRI_USER=your-username
|
|
14
|
+
TRI_PASSWORD=your-password
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# TRIRIGA React App Template
|
|
2
|
+
|
|
3
|
+
This is a template for building IBM TRIRIGA UX applications using React, TypeScript, and Vite.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
1. **Install pnpm** (If not already installed)
|
|
8
|
+
```
|
|
9
|
+
npm install -g pnpm
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
2. **Install Dependencies**
|
|
13
|
+
```
|
|
14
|
+
pnpm install
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## Deployment
|
|
19
|
+
|
|
20
|
+
To deploy to a TRIRIGA environment:
|
|
21
|
+
|
|
22
|
+
1. **Install Depoyment Tool** (If not already installed globally)
|
|
23
|
+
```
|
|
24
|
+
npm install -g @tririga/tri-deploy
|
|
25
|
+
```
|
|
26
|
+
*Note: If you prefer not to install globally, you can add `@tririga/tri-deploy` to your devDependencies.*
|
|
27
|
+
|
|
28
|
+
2. **Configure Credentials**
|
|
29
|
+
Copy `.env.example` to `.env` and update the values for your deployment settings.
|
|
30
|
+
```
|
|
31
|
+
cp .env.example .env
|
|
32
|
+
```
|
|
33
|
+
Ensure your `.env` file has the correct `TRI_URL`, `TRI_USER`, and `TRI_PASSWORD`.
|
|
34
|
+
|
|
35
|
+
3. **Configure tririgaService.js file**
|
|
36
|
+
Update the TriAppConfig values `modelAndView` and `appExposedName` to match your app configuration in TRIRIGA.
|
|
37
|
+
Example:
|
|
38
|
+
`modelAndView: "myTririgaApp",`
|
|
39
|
+
`appExposedName: "myTririgaApp",`
|
|
40
|
+
|
|
41
|
+
4. **Configure vite.config.ts file**
|
|
42
|
+
Update the base value to match your application's exposed name in TRIRIGA.
|
|
43
|
+
Example:
|
|
44
|
+
base: '/app/myReactApp/',
|
|
45
|
+
|
|
46
|
+
5. **Configure package.json**
|
|
47
|
+
"name": "my-tririga-app",
|
|
48
|
+
|
|
49
|
+
*Note: the name should match your Web View Metadata Exposed Name n TRIRIGA*
|
|
50
|
+
|
|
51
|
+
6. **Run Deployment**
|
|
52
|
+
The view name will be taken from the `name` field in `package.json`.
|
|
53
|
+
```
|
|
54
|
+
pnpm run build:deploy
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Browser Support & Polyfills
|
|
58
|
+
|
|
59
|
+
## Project Structure
|
|
60
|
+
|
|
61
|
+
- `src/components`: Reusable UI components.
|
|
62
|
+
- `src/pages`: Top-level page definitions (routes).
|
|
63
|
+
- `src/model`: TRIRIGA AppModel (Datasources).
|
|
64
|
+
- `src/services`: API services (Auth, Config).
|
|
65
|
+
- `public/tri-app-config.json`: Local development configuration mocking the server response.
|
|
66
|
+
|
|
67
|
+
## Tech Stack
|
|
68
|
+
|
|
69
|
+
- [React](https://react.dev/)
|
|
70
|
+
- [TypeScript](https://www.typescriptlang.org/)
|
|
71
|
+
- [Vite](https://vitejs.dev/)
|
|
72
|
+
- [Carbon Design System](https://carbondesignsystem.com/)
|
|
73
|
+
- [@tririga/tririga-react-components](https://www.npmjs.com/package/@tririga/tririga-react-components)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Logs
|
|
2
|
+
logs
|
|
3
|
+
*.log
|
|
4
|
+
npm-debug.log*
|
|
5
|
+
yarn-debug.log*
|
|
6
|
+
yarn-error.log*
|
|
7
|
+
pnpm-debug.log*
|
|
8
|
+
lerna-debug.log*
|
|
9
|
+
|
|
10
|
+
node_modules
|
|
11
|
+
dist
|
|
12
|
+
dist-ssr
|
|
13
|
+
*.local
|
|
14
|
+
.env
|
|
15
|
+
ux-work-task-form
|
|
16
|
+
|
|
17
|
+
# Editor directories and files
|
|
18
|
+
.vscode/*
|
|
19
|
+
!.vscode/extensions.json
|
|
20
|
+
.idea
|
|
21
|
+
.DS_Store
|
|
22
|
+
*.suo
|
|
23
|
+
*.ntvs*
|
|
24
|
+
*.njsproj
|
|
25
|
+
*.sln
|
|
26
|
+
*.sw?
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "new-york",
|
|
4
|
+
"rsc": false,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "src/index.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"iconLibrary": "lucide",
|
|
14
|
+
"rtl": false,
|
|
15
|
+
"aliases": {
|
|
16
|
+
"components": "@/components",
|
|
17
|
+
"utils": "@/lib/utils",
|
|
18
|
+
"ui": "@/components/ui",
|
|
19
|
+
"lib": "@/lib",
|
|
20
|
+
"hooks": "@/hooks"
|
|
21
|
+
},
|
|
22
|
+
"registries": {}
|
|
23
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Tririga React App</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-tririga-app",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"lint": "eslint .",
|
|
10
|
+
"preview": "vite preview",
|
|
11
|
+
"deploy": "dotenv -- cross-var tri-deploy -t %TRI_URL% -u %TRI_USER% -p %TRI_PASSWORD% -v \"$npm_package_name\" -d dist -w",
|
|
12
|
+
"build:deploy": "pnpm run build && pnpm run deploy"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@tririga/tririga-react-components": "^2.0.6",
|
|
16
|
+
"class-variance-authority": "^0.7.1",
|
|
17
|
+
"clsx": "^2.1.1",
|
|
18
|
+
"lodash": "^4.17.21",
|
|
19
|
+
"lodash-es": "^4.17.21",
|
|
20
|
+
"lucide-react": "^0.575.0",
|
|
21
|
+
"react": "^18.3.1",
|
|
22
|
+
"react-dom": "^18.3.1",
|
|
23
|
+
"react-router-dom": "^6.28.0",
|
|
24
|
+
"tailwind-merge": "^3.5.0",
|
|
25
|
+
"tailwindcss": "^4.2.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@eslint/js": "^9.17.0",
|
|
29
|
+
"@types/react": "^18.3.18",
|
|
30
|
+
"@types/react-dom": "^18.3.5",
|
|
31
|
+
"@tailwindcss/vite": "^4.2.0",
|
|
32
|
+
"@types/node": "^22.0.0",
|
|
33
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
34
|
+
"cross-var": "^1.1.0",
|
|
35
|
+
"dotenv-cli": "^8.0.0",
|
|
36
|
+
"eslint": "^9.17.0",
|
|
37
|
+
"eslint-plugin-react-hooks": "^5.0.0",
|
|
38
|
+
"eslint-plugin-react-refresh": "^0.4.16",
|
|
39
|
+
"globals": "^15.14.0",
|
|
40
|
+
"shadcn": "^3.8.5",
|
|
41
|
+
"typescript": "~5.6.2",
|
|
42
|
+
"typescript-eslint": "^8.18.2",
|
|
43
|
+
"vite": "^6.0.5"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Routes, Route } from 'react-router-dom';
|
|
2
|
+
import { MainLayout } from './components';
|
|
3
|
+
import { HomePage } from './pages';
|
|
4
|
+
|
|
5
|
+
function App() {
|
|
6
|
+
return (
|
|
7
|
+
<Routes>
|
|
8
|
+
<Route path="/" element={<MainLayout />}>
|
|
9
|
+
<Route index element={<HomePage />} />
|
|
10
|
+
</Route>
|
|
11
|
+
</Routes>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default App
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Outlet, Link } from 'react-router-dom';
|
|
3
|
+
import { User, LogOut } from 'lucide-react';
|
|
4
|
+
import { Button } from '@/components/ui/button';
|
|
5
|
+
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
|
6
|
+
import {
|
|
7
|
+
Tooltip,
|
|
8
|
+
TooltipContent,
|
|
9
|
+
TooltipProvider,
|
|
10
|
+
TooltipTrigger,
|
|
11
|
+
} from '@/components/ui/tooltip';
|
|
12
|
+
import { AuthService } from '../services/AuthService';
|
|
13
|
+
|
|
14
|
+
const MainLayout: React.FC = () => {
|
|
15
|
+
const userName = 'User';
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className="min-h-screen flex flex-col">
|
|
19
|
+
<header className="sticky top-0 z-50 flex items-center justify-between px-6 h-12 bg-[#001529]">
|
|
20
|
+
<Link to="/" className="no-underline flex items-center gap-2">
|
|
21
|
+
<span className="font-semibold text-white/65 text-sm">TRIRIGA</span>
|
|
22
|
+
<span className="text-white text-sm">React App</span>
|
|
23
|
+
</Link>
|
|
24
|
+
|
|
25
|
+
<TooltipProvider>
|
|
26
|
+
<div className="flex items-center gap-1">
|
|
27
|
+
<Tooltip>
|
|
28
|
+
<TooltipTrigger asChild>
|
|
29
|
+
<Button variant="ghost" size="icon" className="text-white/85 hover:text-white hover:bg-white/10 h-8 w-8">
|
|
30
|
+
<Avatar className="h-6 w-6 bg-transparent">
|
|
31
|
+
<AvatarFallback className="bg-transparent text-white/85 text-xs">
|
|
32
|
+
<User className="h-4 w-4" />
|
|
33
|
+
</AvatarFallback>
|
|
34
|
+
</Avatar>
|
|
35
|
+
</Button>
|
|
36
|
+
</TooltipTrigger>
|
|
37
|
+
<TooltipContent>{userName}</TooltipContent>
|
|
38
|
+
</Tooltip>
|
|
39
|
+
|
|
40
|
+
<Tooltip>
|
|
41
|
+
<TooltipTrigger asChild>
|
|
42
|
+
<Button
|
|
43
|
+
variant="ghost"
|
|
44
|
+
size="icon"
|
|
45
|
+
className="text-white/85 hover:text-white hover:bg-white/10 h-8 w-8"
|
|
46
|
+
onClick={() => AuthService.logout()}
|
|
47
|
+
>
|
|
48
|
+
<LogOut className="h-4 w-4" />
|
|
49
|
+
</Button>
|
|
50
|
+
</TooltipTrigger>
|
|
51
|
+
<TooltipContent>Sign out</TooltipContent>
|
|
52
|
+
</Tooltip>
|
|
53
|
+
</div>
|
|
54
|
+
</TooltipProvider>
|
|
55
|
+
</header>
|
|
56
|
+
|
|
57
|
+
<main className="flex-1 p-6 bg-background min-h-[calc(100vh-48px)]">
|
|
58
|
+
<Outlet />
|
|
59
|
+
</main>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export default MainLayout;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as MainLayout } from './MainLayout';
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { ChevronDownIcon } from "lucide-react"
|
|
3
|
+
import { Accordion as AccordionPrimitive } from "radix-ui"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
function Accordion({
|
|
8
|
+
...props
|
|
9
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
|
|
10
|
+
return <AccordionPrimitive.Root data-slot="accordion" {...props} />
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function AccordionItem({
|
|
14
|
+
className,
|
|
15
|
+
...props
|
|
16
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
|
|
17
|
+
return (
|
|
18
|
+
<AccordionPrimitive.Item
|
|
19
|
+
data-slot="accordion-item"
|
|
20
|
+
className={cn("border-b last:border-b-0", className)}
|
|
21
|
+
{...props}
|
|
22
|
+
/>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function AccordionTrigger({
|
|
27
|
+
className,
|
|
28
|
+
children,
|
|
29
|
+
...props
|
|
30
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
|
|
31
|
+
return (
|
|
32
|
+
<AccordionPrimitive.Header className="flex">
|
|
33
|
+
<AccordionPrimitive.Trigger
|
|
34
|
+
data-slot="accordion-trigger"
|
|
35
|
+
className={cn(
|
|
36
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
|
|
37
|
+
className
|
|
38
|
+
)}
|
|
39
|
+
{...props}
|
|
40
|
+
>
|
|
41
|
+
{children}
|
|
42
|
+
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
|
|
43
|
+
</AccordionPrimitive.Trigger>
|
|
44
|
+
</AccordionPrimitive.Header>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function AccordionContent({
|
|
49
|
+
className,
|
|
50
|
+
children,
|
|
51
|
+
...props
|
|
52
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
|
|
53
|
+
return (
|
|
54
|
+
<AccordionPrimitive.Content
|
|
55
|
+
data-slot="accordion-content"
|
|
56
|
+
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
|
|
57
|
+
{...props}
|
|
58
|
+
>
|
|
59
|
+
<div className={cn("pt-0 pb-4", className)}>{children}</div>
|
|
60
|
+
</AccordionPrimitive.Content>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Avatar as AvatarPrimitive } from "radix-ui"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
function Avatar({
|
|
9
|
+
className,
|
|
10
|
+
size = "default",
|
|
11
|
+
...props
|
|
12
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Root> & {
|
|
13
|
+
size?: "default" | "sm" | "lg"
|
|
14
|
+
}) {
|
|
15
|
+
return (
|
|
16
|
+
<AvatarPrimitive.Root
|
|
17
|
+
data-slot="avatar"
|
|
18
|
+
data-size={size}
|
|
19
|
+
className={cn(
|
|
20
|
+
"group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6",
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function AvatarImage({
|
|
29
|
+
className,
|
|
30
|
+
...props
|
|
31
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
|
32
|
+
return (
|
|
33
|
+
<AvatarPrimitive.Image
|
|
34
|
+
data-slot="avatar-image"
|
|
35
|
+
className={cn("aspect-square size-full", className)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function AvatarFallback({
|
|
42
|
+
className,
|
|
43
|
+
...props
|
|
44
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
|
45
|
+
return (
|
|
46
|
+
<AvatarPrimitive.Fallback
|
|
47
|
+
data-slot="avatar-fallback"
|
|
48
|
+
className={cn(
|
|
49
|
+
"bg-muted text-muted-foreground flex size-full items-center justify-center rounded-full text-sm group-data-[size=sm]/avatar:text-xs",
|
|
50
|
+
className
|
|
51
|
+
)}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
|
|
58
|
+
return (
|
|
59
|
+
<span
|
|
60
|
+
data-slot="avatar-badge"
|
|
61
|
+
className={cn(
|
|
62
|
+
"bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full ring-2 select-none",
|
|
63
|
+
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
|
|
64
|
+
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
|
|
65
|
+
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
|
|
66
|
+
className
|
|
67
|
+
)}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
74
|
+
return (
|
|
75
|
+
<div
|
|
76
|
+
data-slot="avatar-group"
|
|
77
|
+
className={cn(
|
|
78
|
+
"*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2",
|
|
79
|
+
className
|
|
80
|
+
)}
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function AvatarGroupCount({
|
|
87
|
+
className,
|
|
88
|
+
...props
|
|
89
|
+
}: React.ComponentProps<"div">) {
|
|
90
|
+
return (
|
|
91
|
+
<div
|
|
92
|
+
data-slot="avatar-group-count"
|
|
93
|
+
className={cn(
|
|
94
|
+
"bg-muted text-muted-foreground ring-background relative flex size-8 shrink-0 items-center justify-center rounded-full text-sm ring-2 group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
|
|
95
|
+
className
|
|
96
|
+
)}
|
|
97
|
+
{...props}
|
|
98
|
+
/>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export {
|
|
103
|
+
Avatar,
|
|
104
|
+
AvatarImage,
|
|
105
|
+
AvatarFallback,
|
|
106
|
+
AvatarBadge,
|
|
107
|
+
AvatarGroup,
|
|
108
|
+
AvatarGroupCount,
|
|
109
|
+
}
|