@xeplr/cli 0.3.0 → 0.3.2
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 +1 -15
- package/lib/generate/index.js +1 -63
- package/lib/questions.js +0 -4
- package/package.json +1 -1
- package/templates/CLAUDE.md +0 -1
- package/templates/README.md +6 -25
- package/templates/api/README.md +1 -0
- package/templates/api/app.js +0 -4
- package/templates/api/bin/www +1 -15
- package/templates/api/development.env.tpl +7 -2
- package/templates/api/package.json +1 -3
- package/templates/api/production.env.example +4 -6
- package/templates/api/routes/index.js +0 -5
- package/templates/ui/dotenv.tpl +0 -1
- package/templates/ui/package.json +0 -1
- package/templates/ui/src/App.jsx +0 -20
- package/templates/ui/src/index.css +0 -16
- package/templates/ui/src/menu.js +0 -1
- package/templates/ui/src/pages/ConfigureUI.jsx +5 -4
- package/templates/ui/src/pages/Forms.jsx +120 -17
- package/templates/ui/src/pages/Masters.jsx +114 -0
- package/templates/ui/vite.config.js +1 -7
- package/templates/api/bin/workflow +0 -52
- package/templates/api/migrations-auth/0005_flows_menu.sql +0 -14
- package/templates/api/routes/flows.js +0 -47
- package/templates/api/workflow.js +0 -34
- package/templates/ui/src/api/flows.js +0 -8
- package/templates/ui/src/pages/FlowDesigner.jsx +0 -54
- package/templates/ui/src/pages/Flows.jsx +0 -125
- package/templates/ui/src/pages/FlowsHome.jsx +0 -65
- package/templates/ui/src/pages/Journey.jsx +0 -33
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
// CONFIGURE UI → FLOWS — every journey across the app's forms.
|
|
2
|
-
//
|
|
3
|
-
// A flow is what someone is walked through: screens one after another, and
|
|
4
|
-
// whatever has to happen between them. What they fill in on one decides which
|
|
5
|
-
// comes next. Xeplr Workflow runs it, so a journey can be left and picked up
|
|
6
|
-
// later — by someone else, on another day. Design it here; open it at
|
|
7
|
-
// /journey/<key>.
|
|
8
|
-
//
|
|
9
|
-
// A FLOW IS AN ORDINARY WORKFLOW, addressed by a key this page makes from its
|
|
10
|
-
// name. It used to be created as kind 'screens', which is a workflow whose
|
|
11
|
-
// steps are GENERATED from a design held in another app — and that app was
|
|
12
|
-
// the screens-only builder this one replaced. The designer then had to refuse
|
|
13
|
-
// to edit its own flows ("designed in Configure UI"), which is how a flow
|
|
14
|
-
// became a page you could open and not change.
|
|
15
|
-
//
|
|
16
|
-
// So a flow is a workflow now, and every kind of step is available inside it:
|
|
17
|
-
// a Screen for the part a person fills in, an Action for the email that goes
|
|
18
|
-
// out after it, a Condition for where it goes next. Flows created the old way
|
|
19
|
-
// still appear here and still run; the designer shows them read-only.
|
|
20
|
-
import { useEffect, useState } from 'react'
|
|
21
|
-
import { Link, useNavigate } from 'react-router-dom'
|
|
22
|
-
import { listWorkflows, saveWorkflow, configureWorkflowApi } from '@xeplr/ui-workflow'
|
|
23
|
-
|
|
24
|
-
// The same mount the designer uses. Said here as well because this page can
|
|
25
|
-
// be the first one to ask, and a base set after the first request is too late.
|
|
26
|
-
configureWorkflowApi('/api/workflow')
|
|
27
|
-
|
|
28
|
-
export default function Flows() {
|
|
29
|
-
const [list, setList] = useState(null)
|
|
30
|
-
const [error, setError] = useState(null)
|
|
31
|
-
const [offline, setOffline] = useState(null) // API_NOT_RESTARTED
|
|
32
|
-
const [name, setName] = useState('')
|
|
33
|
-
const navigate = useNavigate()
|
|
34
|
-
|
|
35
|
-
const load = () => listWorkflows().then(setList, (e) => {
|
|
36
|
-
const off = flowsOffline(e)
|
|
37
|
-
if (off) setOffline(off)
|
|
38
|
-
else setError(e.message)
|
|
39
|
-
})
|
|
40
|
-
useEffect(() => { load() }, [])
|
|
41
|
-
|
|
42
|
-
const create = async (e) => {
|
|
43
|
-
e.preventDefault()
|
|
44
|
-
const label = name.trim()
|
|
45
|
-
if (!label) return
|
|
46
|
-
const key = label.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '')
|
|
47
|
-
try {
|
|
48
|
-
// No steps: the designer is where those come from, and it opens on the
|
|
49
|
-
// flow this creates. `key` is what every link to it uses from here on.
|
|
50
|
-
await saveWorkflow({ name: label, key, kind: 'workflow', status: 'draft' })
|
|
51
|
-
navigate(`/configure/flows/${key}`)
|
|
52
|
-
} catch (err) {
|
|
53
|
-
setError(err.message)
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (offline) return <NotConnected offline={offline} />
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<div className="app-forms">
|
|
61
|
-
<p className="app-page-lead">
|
|
62
|
-
A flow is your forms, one after another — what someone fills in decides the next one.
|
|
63
|
-
Xeplr Workflow runs it, so a journey can be left and picked up later.
|
|
64
|
-
</p>
|
|
65
|
-
{error && <div className="app-error">{error}</div>}
|
|
66
|
-
|
|
67
|
-
<form className="app-forms-new" onSubmit={create}>
|
|
68
|
-
<label className="app-forms-field">
|
|
69
|
-
<span>Name</span>
|
|
70
|
-
<input value={name} onChange={(e) => setName(e.target.value)} placeholder="Employee registration" />
|
|
71
|
-
</label>
|
|
72
|
-
<button type="submit" className="app-btn" disabled={!name.trim()}>New flow</button>
|
|
73
|
-
</form>
|
|
74
|
-
|
|
75
|
-
{list && list.length === 0 && <p className="app-sub">No flows yet.</p>}
|
|
76
|
-
{list && list.length > 0 && (
|
|
77
|
-
<table className="app-table">
|
|
78
|
-
<thead><tr><th>Flow</th><th>Key</th><th>Status</th><th>Screens</th><th /></tr></thead>
|
|
79
|
-
<tbody>
|
|
80
|
-
{list.map((f) => (
|
|
81
|
-
<tr key={f.id || f.key}>
|
|
82
|
-
<td><strong>{f.name}</strong></td>
|
|
83
|
-
<td className="app-sub">{f.key || '—'}</td>
|
|
84
|
-
<td>{f.kind === 'screens' ? f.status + ' · read-only' : f.status}</td>
|
|
85
|
-
<td>{typeof f.steps === 'number' ? f.steps : (f.steps || []).length}</td>
|
|
86
|
-
<td className="app-row-actions">
|
|
87
|
-
{f.key && <Link to={`/configure/flows/${f.key}`}>Design</Link>}
|
|
88
|
-
{/* PUBLISHING IS A SCREENS-FLOW IDEA: there, publish is what
|
|
89
|
-
checks the design before somebody is walked through it.
|
|
90
|
-
A flow drawn in the designer has no such step, so it can
|
|
91
|
-
be opened as soon as it has one. */}
|
|
92
|
-
{(f.kind === 'screens' ? f.status === 'published' : (f.steps || []).length > 0) && f.key && (
|
|
93
|
-
<Link to={`/journey/${f.key}`}>Open</Link>
|
|
94
|
-
)}
|
|
95
|
-
</td>
|
|
96
|
-
</tr>
|
|
97
|
-
))}
|
|
98
|
-
</tbody>
|
|
99
|
-
</table>
|
|
100
|
-
)}
|
|
101
|
-
</div>
|
|
102
|
-
)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Why flows cannot run right now, or null when the error is something else.
|
|
107
|
-
* Workflow runs inside this app's API, so there is one such case left: an API
|
|
108
|
-
* started before flows existed (404 on /api/flows) — restart it.
|
|
109
|
-
*/
|
|
110
|
-
export function flowsOffline(err) {
|
|
111
|
-
if (err && err.status === 404) return { code: 'API_NOT_RESTARTED', message: 'This app\'s API does not have the flows route yet.' }
|
|
112
|
-
return null
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/** Flows before the API has been restarted with them. */
|
|
116
|
-
export function NotConnected({ offline }) {
|
|
117
|
-
return (
|
|
118
|
-
<div className="app-forms">
|
|
119
|
-
<div className="app-connect">
|
|
120
|
-
<h2>Restart the API to use flows</h2>
|
|
121
|
-
<p>{offline.message} It was started before flows were added — restart it, then reload this page.</p>
|
|
122
|
-
</div>
|
|
123
|
-
</div>
|
|
124
|
-
)
|
|
125
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
// FLOWS — the side-rail page: the journeys people can start, one click each.
|
|
2
|
-
//
|
|
3
|
-
// Always in the rail. Flows are run by @xeplr/workflow inside this app's own
|
|
4
|
-
// API, so they are simply there. Designing them is Configure UI → Flows.
|
|
5
|
-
import { useEffect, useState } from 'react'
|
|
6
|
-
import { Link, useNavigate } from 'react-router-dom'
|
|
7
|
-
import { useAccess } from '@xeplr/ui-account'
|
|
8
|
-
import { flows } from '../api/flows.js'
|
|
9
|
-
import { NotConnected, flowsOffline } from './Flows.jsx'
|
|
10
|
-
|
|
11
|
-
export default function FlowsHome() {
|
|
12
|
-
const [list, setList] = useState(null)
|
|
13
|
-
const [error, setError] = useState(null)
|
|
14
|
-
const [offline, setOffline] = useState(null)
|
|
15
|
-
const navigate = useNavigate()
|
|
16
|
-
const { access } = useAccess()
|
|
17
|
-
// Whoever may open Configure UI may design flows and connect Workflow.
|
|
18
|
-
const admin = ((access && access.menus) || []).includes('Configure UI')
|
|
19
|
-
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
flows.listFlows().then(setList, (e) => {
|
|
22
|
-
const off = flowsOffline(e)
|
|
23
|
-
if (off) setOffline(off)
|
|
24
|
-
else setError(e.message)
|
|
25
|
-
})
|
|
26
|
-
}, [])
|
|
27
|
-
|
|
28
|
-
const published = (list || []).filter((f) => f.status === 'published')
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<div className="app-page">
|
|
32
|
-
<h1>Flows</h1>
|
|
33
|
-
<p className="app-page-lead">
|
|
34
|
-
Forms one after another — what you fill in on one decides which comes next. A journey you
|
|
35
|
-
leave is kept, and picks up where it stopped.
|
|
36
|
-
</p>
|
|
37
|
-
{offline && <NotConnected offline={offline} />}
|
|
38
|
-
{error && <div className="app-error">{error}</div>}
|
|
39
|
-
{list && published.length === 0 && (
|
|
40
|
-
<p className="app-sub">
|
|
41
|
-
No flows to start yet.{admin && <> <Link to="/configure?tab=flows">Design one</Link> in Configure UI.</>}
|
|
42
|
-
</p>
|
|
43
|
-
)}
|
|
44
|
-
{published.length > 0 && (
|
|
45
|
-
<table className="app-table">
|
|
46
|
-
<thead><tr><th>Flow</th><th>Screens</th><th /></tr></thead>
|
|
47
|
-
<tbody>
|
|
48
|
-
{published.map((f) => (
|
|
49
|
-
<tr key={f.key}>
|
|
50
|
-
<td><strong>{f.name}</strong></td>
|
|
51
|
-
<td>{typeof f.steps === 'number' ? f.steps : (f.steps || []).length}</td>
|
|
52
|
-
<td className="app-row-actions">
|
|
53
|
-
<button type="button" className="app-btn" onClick={() => navigate(`/journey/${f.key}`)}>Start</button>
|
|
54
|
-
</td>
|
|
55
|
-
</tr>
|
|
56
|
-
))}
|
|
57
|
-
</tbody>
|
|
58
|
-
</table>
|
|
59
|
-
)}
|
|
60
|
-
{admin && list && published.length > 0 && (
|
|
61
|
-
<p className="app-sub" style={{ marginTop: 16 }}><Link to="/configure?tab=flows">Design flows</Link> in Configure UI.</p>
|
|
62
|
-
)}
|
|
63
|
-
</div>
|
|
64
|
-
)
|
|
65
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// A FLOW, being walked through — /journey/<key>, and /journey/<key>/<run> once
|
|
2
|
-
// it has started. The run id in the address is what lets a journey be
|
|
3
|
-
// reloaded, left, or handed to someone else and opened on the step it is on.
|
|
4
|
-
//
|
|
5
|
-
// This page holds no route through the journey: Workflow says which screen
|
|
6
|
-
// comes next, from what was filled in.
|
|
7
|
-
import { useNavigate, useParams } from 'react-router-dom'
|
|
8
|
-
import { FlowRunner } from '@xeplr/ui-factory'
|
|
9
|
-
import { flows } from '../api/flows.js'
|
|
10
|
-
import { factory } from '../api/factory.js'
|
|
11
|
-
|
|
12
|
-
export default function Journey() {
|
|
13
|
-
const { flow, run } = useParams()
|
|
14
|
-
const navigate = useNavigate()
|
|
15
|
-
return (
|
|
16
|
-
<div className="app-page">
|
|
17
|
-
<FlowRunner
|
|
18
|
-
{...flows.runnerProps}
|
|
19
|
-
{...factory.screenProps}
|
|
20
|
-
flowKey={flow}
|
|
21
|
-
runId={run}
|
|
22
|
-
onStep={(step) => { if (step.runId && step.runId !== run) navigate(`/journey/${flow}/${step.runId}`, { replace: true }) }}
|
|
23
|
-
renderDone={() => (
|
|
24
|
-
<div className="app-help">
|
|
25
|
-
<h2>All done</h2>
|
|
26
|
-
<p>Everything in this journey is filled in.</p>
|
|
27
|
-
<button type="button" className="app-btn" onClick={() => navigate(`/journey/${flow}`)}>Start another</button>
|
|
28
|
-
</div>
|
|
29
|
-
)}
|
|
30
|
-
/>
|
|
31
|
-
</div>
|
|
32
|
-
)
|
|
33
|
-
}
|