onflyt-cli 0.1.0-beta

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.
@@ -0,0 +1,287 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { Text, Box, useInput } from "ink";
3
+ import { isLoggedIn, getConfig } from "../lib/config.js";
4
+ import { api } from "../lib/api.js";
5
+ import { Logo } from "../components/Loading.js";
6
+ import Spinner from "ink-spinner";
7
+ import { FRAMEWORKS } from "../shared";
8
+
9
+ type Step =
10
+ | "loading"
11
+ | "loading-projects"
12
+ | "team"
13
+ | "project"
14
+ | "display"
15
+ | "error";
16
+
17
+ const ProjectsList = () => {
18
+ const [step, setStep] = useState<Step>("loading");
19
+
20
+ const [teams, setTeams] = useState<any[]>([]);
21
+ const [projects, setProjects] = useState<any[]>([]);
22
+
23
+ const [selectedTeamIndex, setSelectedTeamIndex] = useState(0);
24
+ const [selectedProjectIndex, setSelectedProjectIndex] = useState(0);
25
+ const [errorMsg, setErrorMsg] = useState("");
26
+
27
+ useEffect(() => {
28
+ const handleSigInt = () => process.exit(0);
29
+ process.on("SIGINT", handleSigInt);
30
+ return () => {
31
+ process.off("SIGINT", handleSigInt);
32
+ };
33
+ }, []);
34
+
35
+ useInput((input, key) => {
36
+ if (input === "q" || input === "Q" || (key.ctrl && input === "c")) {
37
+ process.exit(0);
38
+ }
39
+
40
+ if (step === "team") {
41
+ if (key.upArrow) {
42
+ setSelectedTeamIndex((i) => Math.max(0, i - 1));
43
+ } else if (key.downArrow) {
44
+ setSelectedTeamIndex((i) => Math.min(teams.length - 1, i + 1));
45
+ } else if (key.return) {
46
+ setStep("loading-projects");
47
+ loadProjects(teams[selectedTeamIndex].team.id);
48
+ }
49
+ }
50
+
51
+ if (step === "project") {
52
+ if (key.upArrow) {
53
+ setSelectedProjectIndex((i) => Math.max(0, i - 1));
54
+ } else if (key.downArrow) {
55
+ setSelectedProjectIndex((i) => Math.min(projects.length - 1, i + 1));
56
+ } else if (key.return) {
57
+ setStep("display");
58
+ } else if (key.escape) {
59
+ setStep("team");
60
+ }
61
+ }
62
+ });
63
+
64
+ useEffect(() => {
65
+ if (!isLoggedIn()) {
66
+ setErrorMsg("Not logged in. Run 'onflyt login' first.");
67
+ setStep("error");
68
+ return;
69
+ }
70
+
71
+ loadTeams();
72
+ }, []);
73
+
74
+ const loadTeams = async () => {
75
+ try {
76
+ const config = getConfig();
77
+ api.setToken(config.token!);
78
+ const meData = await api.get<any>("/auth/me");
79
+ const userTeams = meData.teams || [];
80
+
81
+ if (userTeams.length === 0) {
82
+ setErrorMsg("No teams found");
83
+ setStep("error");
84
+ return;
85
+ }
86
+
87
+ setTeams(userTeams);
88
+
89
+ if (userTeams.length === 1) {
90
+ setSelectedTeamIndex(0);
91
+ setStep("loading-projects");
92
+ loadProjects(userTeams[0].team.id);
93
+ } else {
94
+ setStep("team");
95
+ }
96
+ } catch (err: any) {
97
+ setErrorMsg(err.message);
98
+ setStep("error");
99
+ }
100
+ };
101
+
102
+ const loadProjects = async (teamId: string) => {
103
+ try {
104
+ const projectsRes = await api.get<any>(`/projects/team/${teamId}`);
105
+ const teamProjects = projectsRes.projects || [];
106
+
107
+ setProjects(teamProjects);
108
+ setSelectedProjectIndex(0);
109
+ setStep("project");
110
+ } catch (err: any) {
111
+ setErrorMsg(err.message);
112
+ setStep("error");
113
+ }
114
+ };
115
+
116
+ if (step === "loading" || step === "loading-projects") {
117
+ return (
118
+ <Box flexDirection="column" padding={1}>
119
+ <Logo />
120
+ <Box marginTop={1}>
121
+ <Text bold>Projects</Text>
122
+ </Box>
123
+ <Box marginTop={1}>
124
+ <Text dimColor>
125
+ Loading{step === "loading-projects" ? " projects..." : "..."}
126
+ </Text>
127
+ </Box>
128
+ <Box marginTop={1}>
129
+ <Text>
130
+ <Spinner />
131
+ </Text>
132
+ </Box>
133
+ </Box>
134
+ );
135
+ }
136
+
137
+ if (step === "error") {
138
+ return (
139
+ <Box flexDirection="column">
140
+ <Logo />
141
+ <Box marginTop={1}>
142
+ <Text bold color="red">
143
+ ✖ Error
144
+ </Text>
145
+ </Box>
146
+ <Box marginTop={1}>
147
+ <Text color="red">{errorMsg}</Text>
148
+ </Box>
149
+ </Box>
150
+ );
151
+ }
152
+
153
+ if (step === "team") {
154
+ return (
155
+ <Box flexDirection="column" padding={1}>
156
+ <Logo />
157
+ <Box marginTop={1}>
158
+ <Text bold>Projects</Text>
159
+ </Box>
160
+ <Box marginTop={1}>
161
+ <Text dimColor>
162
+ Step 1/2: Select Team (↑↓ navigate, Enter select)
163
+ </Text>
164
+ </Box>
165
+
166
+ <Box marginTop={1} flexDirection="column">
167
+ {teams.map((t, idx) => (
168
+ <Box key={t.team.id} marginTop={1}>
169
+ <Text color={idx === selectedTeamIndex ? "cyan" : "gray"}>
170
+ {idx === selectedTeamIndex ? "▶ " : " "}
171
+ </Text>
172
+ <Text bold={idx === selectedTeamIndex}>{t.team.name}</Text>
173
+ </Box>
174
+ ))}
175
+ </Box>
176
+ </Box>
177
+ );
178
+ }
179
+
180
+ if (step === "project") {
181
+ return (
182
+ <Box flexDirection="column" padding={1}>
183
+ <Logo />
184
+ <Box marginTop={1}>
185
+ <Text bold>Projects</Text>
186
+ </Box>
187
+ <Box marginTop={1}>
188
+ <Text dimColor>
189
+ Step 2/2: Select Project - {teams[selectedTeamIndex]?.team.name}
190
+ </Text>
191
+ </Box>
192
+ <Box>
193
+ <Text dimColor>(↑↓ navigate, Enter view details, Esc go back)</Text>
194
+ </Box>
195
+
196
+ <Box marginTop={1} flexDirection="column">
197
+ {projects.length === 0 ? (
198
+ <Text dimColor>No projects in this team</Text>
199
+ ) : (
200
+ projects.map((p, idx) => (
201
+ <Box key={p.id} marginTop={1}>
202
+ <Text color={idx === selectedProjectIndex ? "cyan" : "gray"}>
203
+ {idx === selectedProjectIndex ? "▶ " : " "}
204
+ </Text>
205
+ <Text bold={idx === selectedProjectIndex}>{p.name}</Text>
206
+ <Text dimColor>
207
+ {" "}
208
+ ({FRAMEWORKS[p.framework]?.label || p.framework})
209
+ </Text>
210
+ <Text dimColor>
211
+ {" "}
212
+ - {p.status === "active" ? "●" : "○"} {p.status}
213
+ </Text>
214
+ </Box>
215
+ ))
216
+ )}
217
+ </Box>
218
+ </Box>
219
+ );
220
+ }
221
+
222
+ const selectedProject = projects[selectedProjectIndex];
223
+
224
+ return (
225
+ <Box flexDirection="column" padding={1}>
226
+ <Logo />
227
+
228
+ <Box marginTop={1}>
229
+ <Text bold>Project Details</Text>
230
+ </Box>
231
+ <Box>
232
+ <Text dimColor>
233
+ {teams[selectedTeamIndex]?.team.name} / {selectedProject?.name}
234
+ </Text>
235
+ </Box>
236
+
237
+ {selectedProject && (
238
+ <Box
239
+ marginTop={1}
240
+ flexDirection="column"
241
+ borderStyle="round"
242
+ borderDimColor
243
+ paddingX={1}
244
+ >
245
+ <Box marginTop={1}>
246
+ <Text bold>Name:</Text>
247
+ <Text> {selectedProject.name}</Text>
248
+ </Box>
249
+ <Box marginTop={1}>
250
+ <Text bold>Framework:</Text>
251
+ <Text>
252
+ {" "}
253
+ {FRAMEWORKS[selectedProject.framework]?.label ||
254
+ selectedProject.framework}
255
+ </Text>
256
+ </Box>
257
+ <Box marginTop={1}>
258
+ <Text bold>Status:</Text>
259
+ <Text> {selectedProject.status}</Text>
260
+ </Box>
261
+ <Box marginTop={1}>
262
+ <Text bold>ID:</Text>
263
+ <Text dimColor> {selectedProject.id}</Text>
264
+ </Box>
265
+ {selectedProject.gitRepoUrl && (
266
+ <Box marginTop={1}>
267
+ <Text bold>Repository:</Text>
268
+ <Text color="cyan"> {selectedProject.gitRepoUrl}</Text>
269
+ </Box>
270
+ )}
271
+ {selectedProject.outputDirectory && (
272
+ <Box marginTop={1}>
273
+ <Text bold>Output:</Text>
274
+ <Text dimColor> {selectedProject.outputDirectory}</Text>
275
+ </Box>
276
+ )}
277
+ </Box>
278
+ )}
279
+
280
+ <Box marginTop={1}>
281
+ <Text dimColor>↑↓ navigate projects | Esc go back</Text>
282
+ </Box>
283
+ </Box>
284
+ );
285
+ };
286
+
287
+ export default ProjectsList;