@monimesl/dot-cli 0.1.0-alpha.2 → 0.1.0-alpha.4

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.
Files changed (68) hide show
  1. package/README.md +34 -15
  2. package/dist/cli.js +386 -57
  3. package/dist/cli.js.map +1 -1
  4. package/dist/server-DFOUQnLA.js +11263 -0
  5. package/dist/server-DFOUQnLA.js.map +1 -0
  6. package/dist/templates/showcase/.env +3 -0
  7. package/dist/templates/showcase/dot.ts +22 -0
  8. package/dist/templates/showcase/pages/carousel/controller.ts +10 -0
  9. package/dist/templates/showcase/pages/carousel/state.ts +40 -0
  10. package/dist/templates/showcase/pages/carousel/view.dml +37 -0
  11. package/dist/templates/showcase/pages/device/controller.ts +70 -0
  12. package/dist/templates/showcase/pages/device/state.ts +7 -0
  13. package/dist/templates/showcase/pages/device/view.dml +44 -0
  14. package/dist/templates/showcase/pages/feedback/controller.ts +16 -0
  15. package/dist/templates/showcase/pages/feedback/state.ts +7 -0
  16. package/dist/templates/showcase/pages/feedback/view.dml +87 -0
  17. package/dist/templates/showcase/pages/files/controller.ts +47 -0
  18. package/dist/templates/showcase/pages/files/state.ts +11 -0
  19. package/dist/templates/showcase/pages/files/view.dml +35 -0
  20. package/dist/templates/showcase/pages/filters/controller.ts +28 -0
  21. package/dist/templates/showcase/pages/filters/state.ts +72 -0
  22. package/dist/templates/showcase/pages/filters/view.dml +43 -0
  23. package/dist/templates/showcase/pages/forms/controller.ts +44 -0
  24. package/dist/templates/showcase/pages/forms/state.ts +15 -0
  25. package/dist/templates/showcase/pages/forms/view.dml +39 -0
  26. package/dist/templates/showcase/pages/images/controller.ts +15 -0
  27. package/dist/templates/showcase/pages/images/state.ts +7 -0
  28. package/dist/templates/showcase/pages/images/view.dml +38 -0
  29. package/dist/templates/showcase/pages/index/controller.ts +10 -0
  30. package/dist/templates/showcase/pages/index/state.ts +116 -0
  31. package/dist/templates/showcase/pages/index/view.dml +20 -0
  32. package/dist/templates/showcase/pages/layout/controller.ts +26 -0
  33. package/dist/templates/showcase/pages/layout/state.ts +14 -0
  34. package/dist/templates/showcase/pages/layout/view.dml +80 -0
  35. package/dist/templates/showcase/pages/lists/controller.ts +36 -0
  36. package/dist/templates/showcase/pages/lists/state.ts +96 -0
  37. package/dist/templates/showcase/pages/lists/view.dml +59 -0
  38. package/dist/templates/showcase/pages/navigation/controller.ts +64 -0
  39. package/dist/templates/showcase/pages/navigation/state.ts +17 -0
  40. package/dist/templates/showcase/pages/navigation/view.dml +34 -0
  41. package/dist/templates/showcase/pages/network/controller.ts +107 -0
  42. package/dist/templates/showcase/pages/network/state.ts +15 -0
  43. package/dist/templates/showcase/pages/network/view.dml +56 -0
  44. package/dist/templates/showcase/pages/payments/controller.ts +42 -0
  45. package/dist/templates/showcase/pages/payments/state.ts +10 -0
  46. package/dist/templates/showcase/pages/payments/view.dml +35 -0
  47. package/dist/templates/showcase/pages/state/controller.ts +22 -0
  48. package/dist/templates/showcase/pages/state/state.ts +10 -0
  49. package/dist/templates/showcase/pages/state/view.dml +33 -0
  50. package/dist/templates/showcase/pages/text/controller.ts +10 -0
  51. package/dist/templates/showcase/pages/text/state.ts +7 -0
  52. package/dist/templates/showcase/pages/text/view.dml +49 -0
  53. package/dist/templates/starter/.env +3 -0
  54. package/dist/templates/starter/dot.ts +3 -0
  55. package/dist/templates/starter/pages/index/controller.ts +9 -0
  56. package/dist/templates/starter/pages/index/state.ts +4 -0
  57. package/dist/templates/starter/pages/index/view.dml +16 -0
  58. package/dist/templates/starter/statics/logo.png +0 -0
  59. package/package.json +12 -7
  60. package/dist/template/.env +0 -3
  61. package/dist/template/dot.ts +0 -6
  62. package/dist/template/pages/help/tips/controller.ts +0 -30
  63. package/dist/template/pages/help/tips/state.ts +0 -11
  64. package/dist/template/pages/help/tips/view.dml +0 -15
  65. package/dist/template/pages/index/controller.ts +0 -67
  66. package/dist/template/pages/index/state.ts +0 -17
  67. package/dist/template/pages/index/view.dml +0 -66
  68. /package/dist/{template → templates/showcase}/statics/logo.png +0 -0
@@ -0,0 +1,3 @@
1
+ # Public configuration for the dot. Anyone with the dot can read these variables: never put a
2
+ # secret here. Read them in the service as `env.GREETING`.
3
+ GREETING=This message comes from the dot's .env file.
@@ -0,0 +1,22 @@
1
+ import { logger } from '@monimesl/dot-runtime';
2
+
3
+ export default defineDot({
4
+ pages: [
5
+ 'index',
6
+ 'layout',
7
+ 'text',
8
+ 'images',
9
+ 'carousel',
10
+ 'lists',
11
+ 'filters',
12
+ 'forms',
13
+ 'payments',
14
+ 'feedback',
15
+ 'state',
16
+ 'navigation',
17
+ 'device',
18
+ 'network',
19
+ 'files',
20
+ ],
21
+ plugins: [logger()],
22
+ });
@@ -0,0 +1,10 @@
1
+ import state from './state';
2
+
3
+ export default definePage({
4
+ initialState: state,
5
+ mutations: {
6
+ showTip: (_state, event: DotEvent<{ index: number }>) => ({ tip: event.detail.index }),
7
+ back: (state) => ({ tip: Math.max(state.tip - 1, 0) }),
8
+ next: (state) => ({ tip: Math.min(state.tip + 1, state.tips.length - 1) }),
9
+ },
10
+ });
@@ -0,0 +1,40 @@
1
+ export interface Promo {
2
+ id: string;
3
+ title: string;
4
+ text: string;
5
+ icon: string;
6
+ }
7
+
8
+ export interface Tip {
9
+ title: string;
10
+ text: string;
11
+ }
12
+
13
+ export default {
14
+ promos: [
15
+ {
16
+ id: 'send',
17
+ title: 'Send money home',
18
+ text: 'Free transfers to any mobile wallet this week',
19
+ icon: 'send',
20
+ },
21
+ {
22
+ id: 'bills',
23
+ title: 'Pay bills in seconds',
24
+ text: 'Electricity, water and airtime in one place',
25
+ icon: 'bolt',
26
+ },
27
+ {
28
+ id: 'save',
29
+ title: 'Save a little each day',
30
+ text: 'Set a goal and watch it grow',
31
+ icon: 'wallet',
32
+ },
33
+ ] as Promo[],
34
+ tips: [
35
+ { title: 'Welcome', text: 'Swipe, tap a dot, or use the buttons below.' },
36
+ { title: 'The page decides', text: "The carousel's index comes from state." },
37
+ { title: 'And hears back', text: 'A swipe raises change, which sets the index again.' },
38
+ ] as Tip[],
39
+ tip: 0,
40
+ };
@@ -0,0 +1,37 @@
1
+ <View>
2
+ <Text variant="title">Carousel</Text>
3
+ <Text>A Carousel shows its children one at a time. Swipe between them, or tap a dot.</Text>
4
+
5
+ <View gap="small">
6
+ <Text variant="caption" tone="muted">Plays by itself and loops, pausing while touched, with a gap between slides that scale in as they come</Text>
7
+ <Carousel autoplay loop interval="3000" gap="medium" effect="scale">
8
+ <For each="{{ promos }}" item="promo" key="id">
9
+ <View variant="card" direction="row" align="center" gap="large">
10
+ <Icon name="{{ promo.icon }}" size="large" tone="info" />
11
+ <View gap="small" grow>
12
+ <Text weight="bold">{{ promo.title }}</Text>
13
+ <Text tone="muted">{{ promo.text }}</Text>
14
+ </View>
15
+ </View>
16
+ </For>
17
+ </Carousel>
18
+ </View>
19
+
20
+ <View gap="small">
21
+ <Text variant="caption" tone="muted">The page chooses the slide, and hears when a swipe changes it</Text>
22
+ <Carousel index="{{ tip }}" onChange="showTip">
23
+ <For each="{{ tips }}" item="card" key="title">
24
+ <View variant="card" align="center" gap="medium">
25
+ <Image src="statics/logo.png" alt="" width="48" height="48" />
26
+ <Text weight="bold">{{ card.title }}</Text>
27
+ <Text tone="muted" align="center">{{ card.text }}</Text>
28
+ </View>
29
+ </For>
30
+ </Carousel>
31
+ <View direction="row" gap="small">
32
+ <Button variant="secondary" onClick="back" disabled="{{ tip === 0 }}">Back</Button>
33
+ <Button onClick="next" disabled="{{ tip === tips.length - 1 }}">Next</Button>
34
+ </View>
35
+ <Text>Tip {{ tip + 1 }} of {{ tips.length }}</Text>
36
+ </View>
37
+ </View>
@@ -0,0 +1,70 @@
1
+ import state from './state';
2
+
3
+ export default definePage({
4
+ initialState: state,
5
+ mutations: {
6
+ clearToast: () => ({ toast: '' }),
7
+ },
8
+ actions: {
9
+ async greet() {
10
+ try {
11
+ const { action } = await this.dash.ui.alert({
12
+ title: 'Hello',
13
+ message: env.GREETING,
14
+ actions: [
15
+ { id: 'nice', label: 'Nice' },
16
+ { id: 'close', label: 'Close', role: 'cancel' },
17
+ ],
18
+ });
19
+ this.setState({ choice: action });
20
+ } catch (error) {
21
+ this.setState({ note: (error as Error).message });
22
+ }
23
+ },
24
+ async confirmDelete() {
25
+ try {
26
+ const { action } = await this.dash.ui.alert({
27
+ title: 'Delete everything?',
28
+ message: "This can't be undone.",
29
+ actions: [
30
+ { id: 'keep', label: 'Keep', role: 'cancel' },
31
+ { id: 'delete', label: 'Delete', role: 'destructive' },
32
+ ],
33
+ });
34
+ this.setState({ choice: action });
35
+ } catch (error) {
36
+ this.setState({ note: (error as Error).message });
37
+ }
38
+ },
39
+ async vibrate(_state, event: DotEvent<unknown, { style: 'light' | 'success' | 'error' }>) {
40
+ await this.dash.system.vibrate({ style: event.data.style });
41
+ },
42
+ async copyAccount() {
43
+ await this.dash.system.copyText('AC-2041');
44
+ this.setState({ toast: 'Account number copied' });
45
+ },
46
+ async shareLink() {
47
+ try {
48
+ await this.dash.system.share({ title: 'Dot', url: 'https://github.com/monime-lab/dot' });
49
+ } catch (error) {
50
+ this.setState({ note: (error as Error).message });
51
+ }
52
+ },
53
+ async sendCode() {
54
+ for (let seconds = 10; seconds > 0; seconds--) {
55
+ this.setState({ resendIn: seconds });
56
+ await this.dash.system.delay(1000);
57
+ }
58
+ this.setState({ resendIn: 0 });
59
+ },
60
+ async toggleTorch(state) {
61
+ const next = state.torch === 'on' ? 'off' : 'on';
62
+ try {
63
+ await this.dash.system.torch(next);
64
+ this.setState({ torch: next, note: '' });
65
+ } catch (error) {
66
+ this.setState({ note: (error as Error).message });
67
+ }
68
+ },
69
+ },
70
+ });
@@ -0,0 +1,7 @@
1
+ export default {
2
+ choice: '',
3
+ torch: 'off' as 'on' | 'off',
4
+ note: '',
5
+ toast: '',
6
+ resendIn: 0,
7
+ };
@@ -0,0 +1,44 @@
1
+ <View>
2
+ <Text variant="title">Alerts and device</Text>
3
+ <Text>Dash draws these, not the dot: a dot calls them through the global dash object.</Text>
4
+
5
+ <Text variant="caption" tone="muted">dash.ui.alert</Text>
6
+ <View variant="card" gap="small">
7
+ <View direction="row" wrap gap="small">
8
+ <Button onClick="greet">Say hello</Button>
9
+ <Button variant="secondary" onClick="confirmDelete">Ask to delete</Button>
10
+ </View>
11
+ <If condition="{{ choice }}">
12
+ <Text>You chose "{{ choice }}"</Text>
13
+ </If>
14
+ <Text variant="caption" tone="muted">The hello message is GREETING in the dot's .env file.</Text>
15
+ </View>
16
+
17
+ <Text variant="caption" tone="muted">dash.system.vibrate</Text>
18
+ <View variant="card" direction="row" wrap gap="small">
19
+ <Button variant="secondary" data-style="light" onClick="vibrate">Light</Button>
20
+ <Button variant="secondary" data-style="success" onClick="vibrate">Success</Button>
21
+ <Button variant="secondary" data-style="error" onClick="vibrate">Error</Button>
22
+ </View>
23
+
24
+ <Text variant="caption" tone="muted">dash.system.copyText and dash.system.share</Text>
25
+ <View variant="card" direction="row" wrap gap="small">
26
+ <Button variant="secondary" onClick="copyAccount">Copy AC-2041</Button>
27
+ <Button variant="secondary" onClick="shareLink">Share a link</Button>
28
+ </View>
29
+
30
+ <Text variant="caption" tone="muted">dash.system.delay</Text>
31
+ <View variant="card">
32
+ <Button variant="secondary" onClick="sendCode" disabled="{{ resendIn > 0 }}">{{ resendIn > 0 ? 'Resend in ' + resendIn + 's' : 'Send a code' }}</Button>
33
+ </View>
34
+
35
+ <Text variant="caption" tone="muted">dash.system.torch</Text>
36
+ <View variant="card" gap="small">
37
+ <Button variant="secondary" onClick="toggleTorch">{{ torch === 'on' ? 'Turn the torch off' : 'Turn the torch on' }}</Button>
38
+ <If condition="{{ note }}">
39
+ <Text variant="caption" tone="muted">{{ note }}</Text>
40
+ </If>
41
+ </View>
42
+
43
+ <Toast message="{{ toast }}" tone="success" onHide="clearToast" />
44
+ </View>
@@ -0,0 +1,16 @@
1
+ import state from './state';
2
+
3
+ export default definePage({
4
+ initialState: state,
5
+ mutations: {
6
+ advance: (state) => ({ progress: Math.min(100, state.progress + 20) }),
7
+ restart: () => ({ progress: 0 }),
8
+ toggleBusy: (state) => ({ busy: !state.busy }),
9
+ toggleLoaded: (state) => ({ loaded: !state.loaded }),
10
+ showToast: (_state, event: DotEvent<unknown, { message: string; tone: string }>) => ({
11
+ toast: event.data.message,
12
+ toastTone: event.data.tone,
13
+ }),
14
+ clearToast: () => ({ toast: '' }),
15
+ },
16
+ });
@@ -0,0 +1,7 @@
1
+ export default {
2
+ progress: 40,
3
+ busy: false,
4
+ loaded: false,
5
+ toast: '',
6
+ toastTone: 'default',
7
+ };
@@ -0,0 +1,87 @@
1
+ <View>
2
+ <Text variant="title">Feedback</Text>
3
+ <Text>Show that something is happening, how far along it is, and how it went.</Text>
4
+
5
+ <View gap="small">
6
+ <Text variant="caption" tone="muted">Spinners, while something loads</Text>
7
+ <View variant="card" direction="row" gap="large" align="center">
8
+ <Spinner size="small" />
9
+ <Spinner />
10
+ <Spinner size="large" />
11
+ </View>
12
+ </View>
13
+
14
+ <View gap="small">
15
+ <Text variant="caption" tone="muted">Skeletons, in the shape of what's loading</Text>
16
+ <View variant="card">
17
+ <If condition="{{ loaded }}">
18
+ <View direction="row" align="center" gap="large">
19
+ <Image src="statics/logo.png" alt="Logo" width="40" height="40" shape="circle" />
20
+ <View gap="none">
21
+ <Text weight="medium">Dot Showcase</Text>
22
+ <Text variant="caption" tone="muted">Loaded, in place of the skeleton</Text>
23
+ </View>
24
+ </View>
25
+ <Image src="statics/logo.png" alt="Banner" width="320" height="80" fit="contain" />
26
+ </If>
27
+ <Else>
28
+ <View direction="row" align="center" gap="large">
29
+ <Skeleton shape="circle" />
30
+ <Skeleton lines="2" />
31
+ </View>
32
+ <Skeleton shape="box" height="80" />
33
+ </Else>
34
+ <Switch label="Loaded" checked="{{ loaded }}" onChange="toggleLoaded" />
35
+ </View>
36
+ </View>
37
+
38
+ <View gap="small">
39
+ <Text variant="caption" tone="muted">Toasts, which go away on their own</Text>
40
+ <View direction="row" gap="small">
41
+ <Button variant="secondary" data-message="Saved" data-tone="success" onClick="showToast">Saved</Button>
42
+ <Button variant="secondary" data-message="You're offline" data-tone="warning" onClick="showToast">Offline</Button>
43
+ </View>
44
+ </View>
45
+
46
+ <View gap="small">
47
+ <Text variant="caption" tone="muted">A progress bar, from 0 to 100</Text>
48
+ <View variant="card">
49
+ <ProgressBar value="{{ progress }}" />
50
+ <Text>{{ progress }}% done</Text>
51
+ <View direction="row" gap="small">
52
+ <Button variant="secondary" onClick="advance" disabled="{{ progress >= 100 }}">Advance</Button>
53
+ <Button variant="secondary" onClick="restart" disabled="{{ progress === 0 }}">Restart</Button>
54
+ </View>
55
+ </View>
56
+ </View>
57
+
58
+ <View gap="small">
59
+ <Text variant="caption" tone="muted">A button that's busy ignores taps</Text>
60
+ <View variant="card">
61
+ <Button loading="{{ busy }}">{{ busy ? 'Paying…' : 'Pay Le 50,000' }}</Button>
62
+ <Switch label="Busy" checked="{{ busy }}" onChange="toggleBusy" />
63
+ </View>
64
+ </View>
65
+
66
+ <View gap="small">
67
+ <Text variant="caption" tone="muted">Notices, in the flow of the page</Text>
68
+ <Notice title="Heads up">Your meter number is on the bill.</Notice>
69
+ <Notice tone="info" title="New">You can now pay water bills.</Notice>
70
+ <Notice tone="success" title="Paid">Le 50,000 for electricity.</Notice>
71
+ <Notice tone="warning" title="Almost out">Your wallet has Le 2,000 left.</Notice>
72
+ <Notice tone="danger" title="Payment failed">The bank didn't answer. Try again.</Notice>
73
+ </View>
74
+
75
+ <View gap="small">
76
+ <Text variant="caption" tone="muted">Badges, for a status</Text>
77
+ <View direction="row" wrap gap="small">
78
+ <Badge>Draft</Badge>
79
+ <Badge tone="info">New</Badge>
80
+ <Badge tone="success">Paid</Badge>
81
+ <Badge tone="warning">Pending</Badge>
82
+ <Badge tone="danger">Failed</Badge>
83
+ </View>
84
+ </View>
85
+
86
+ <Toast message="{{ toast }}" tone="{{ toastTone }}" onHide="clearToast" />
87
+ </View>
@@ -0,0 +1,47 @@
1
+ import type { Note } from './state';
2
+ import state from './state';
3
+
4
+ const PICTURE = 'pictures/octocat.png';
5
+
6
+ export default definePage({
7
+ initialState: state,
8
+ hooks: {
9
+ async onShow() {
10
+ await this.actions.refresh();
11
+ },
12
+ },
13
+ mutations: {
14
+ setDraft: (_state, event: DotEvent<{ value: string }>) => ({ draft: event.detail.value }),
15
+ },
16
+ actions: {
17
+ async refresh() {
18
+ const entries = await this.dash.file.list('', { recursive: true });
19
+ const notes: Note[] = [];
20
+ for (const entry of entries) {
21
+ if (entry.kind === 'file' && entry.path.startsWith('notes/')) {
22
+ notes.push({ path: entry.path, text: await this.dash.file.read(entry.path) });
23
+ }
24
+ }
25
+ const { used, quota } = await this.dash.file.usage();
26
+ const kept = await this.dash.file.stat(PICTURE);
27
+ this.setState({
28
+ notes,
29
+ usage: `${used} bytes used of ${quota / 1024 / 1024} MB`,
30
+ picture: kept ? (await this.dash.file.open(PICTURE)).url : '',
31
+ });
32
+ },
33
+ async save(state) {
34
+ await this.dash.file.write(`notes/${Date.now()}.txt`, state.draft);
35
+ this.setState({ draft: '' });
36
+ await this.actions.refresh();
37
+ },
38
+ async clearNotes() {
39
+ await this.dash.file.remove('notes', { recursive: true });
40
+ await this.actions.refresh();
41
+ },
42
+ async forgetPicture() {
43
+ await this.dash.file.remove(PICTURE);
44
+ await this.actions.refresh();
45
+ },
46
+ },
47
+ });
@@ -0,0 +1,11 @@
1
+ export interface Note {
2
+ path: string;
3
+ text: string;
4
+ }
5
+
6
+ export default {
7
+ draft: '',
8
+ notes: [] as Note[],
9
+ usage: '',
10
+ picture: '',
11
+ };
@@ -0,0 +1,35 @@
1
+ <View>
2
+ <Text variant="title">Files</Text>
3
+ <Text>A dot has a folder of its own that no other dot sees. What it keeps there lasts between runs.</Text>
4
+
5
+ <Text variant="caption" tone="muted">Notes, written as text files</Text>
6
+ <View variant="card" gap="small">
7
+ <Input label="Note" placeholder="Buy rice" value="{{ draft }}" maxLength="80" onChange="setDraft" onSubmit="save" />
8
+ <View direction="row" gap="small">
9
+ <Button onClick="save" disabled="{{ !draft }}">Save</Button>
10
+ <Button variant="secondary" onClick="clearNotes" disabled="{{ notes.length === 0 }}">Delete all</Button>
11
+ </View>
12
+ <For each="{{ notes }}" item="note" key="path">
13
+ <View gap="none">
14
+ <Text>{{ note.text }}</Text>
15
+ <Text variant="caption" tone="muted">{{ note.path }}</Text>
16
+ </View>
17
+ </For>
18
+ <If condition="{{ notes.length === 0 }}">
19
+ <Text variant="caption" tone="muted">No notes yet</Text>
20
+ </If>
21
+ </View>
22
+
23
+ <Text variant="caption" tone="muted">A picture kept from the Network page</Text>
24
+ <View variant="card" gap="small">
25
+ <If condition="{{ picture }}">
26
+ <Image src="{{ picture }}" alt="Kept picture" width="64" height="64" />
27
+ <Button variant="secondary" onClick="forgetPicture">Forget it</Button>
28
+ </If>
29
+ <Else>
30
+ <Text variant="caption" tone="muted">None yet: download one on the Network page and tap Keep.</Text>
31
+ </Else>
32
+ </View>
33
+
34
+ <Text variant="caption" tone="muted">{{ usage }}</Text>
35
+ </View>
@@ -0,0 +1,28 @@
1
+ import type { Payment } from './state';
2
+ import state from './state';
3
+
4
+ type Chosen = DotEvent<{ value: string }>;
5
+
6
+ export default definePage({
7
+ initialState: state,
8
+ mutations: {
9
+ chooseTab: (state, event: Chosen) => {
10
+ const tab = event.detail.value;
11
+ return { tab, shown: filter(state.payments, tab, state.status) };
12
+ },
13
+ chooseStatus: (state, event: DotEvent<unknown, { status: string }>) => {
14
+ const { status } = event.data;
15
+ return { status, shown: filter(state.payments, state.tab, status) };
16
+ },
17
+ clearFilters: (state) => ({ tab: 'all', status: 'All', shown: state.payments }),
18
+ choosePeriod: (_state, event: Chosen) => ({ period: event.detail.value }),
19
+ },
20
+ });
21
+
22
+ function filter(payments: readonly Payment[], tab: string, status: string): Payment[] {
23
+ return payments.filter(
24
+ (payment) =>
25
+ (tab === 'all' || payment.direction === tab) &&
26
+ (status === 'All' || payment.status === status),
27
+ );
28
+ }
@@ -0,0 +1,72 @@
1
+ export type Direction = 'in' | 'out';
2
+ export type Status = 'Paid' | 'Pending' | 'Failed';
3
+
4
+ export interface Payment {
5
+ id: string;
6
+ title: string;
7
+ date: string;
8
+ amount: string;
9
+ direction: Direction;
10
+ status: Status;
11
+ tone: string;
12
+ }
13
+
14
+ const payments: Payment[] = [
15
+ {
16
+ id: 'p1',
17
+ title: 'Salary',
18
+ date: 'Today',
19
+ amount: 'Le 2,400,000',
20
+ direction: 'in',
21
+ status: 'Paid',
22
+ tone: 'success',
23
+ },
24
+ {
25
+ id: 'p2',
26
+ title: 'Electricity',
27
+ date: 'Today',
28
+ amount: 'Le 50,000',
29
+ direction: 'out',
30
+ status: 'Paid',
31
+ tone: 'success',
32
+ },
33
+ {
34
+ id: 'p3',
35
+ title: 'From Aminata',
36
+ date: 'Yesterday',
37
+ amount: 'Le 75,000',
38
+ direction: 'in',
39
+ status: 'Pending',
40
+ tone: 'warning',
41
+ },
42
+ {
43
+ id: 'p4',
44
+ title: 'Airtime',
45
+ date: 'Yesterday',
46
+ amount: 'Le 10,000',
47
+ direction: 'out',
48
+ status: 'Pending',
49
+ tone: 'warning',
50
+ },
51
+ {
52
+ id: 'p5',
53
+ title: 'Water',
54
+ date: '12 September',
55
+ amount: 'Le 35,000',
56
+ direction: 'out',
57
+ status: 'Failed',
58
+ tone: 'danger',
59
+ },
60
+ ];
61
+
62
+ export default {
63
+ payments,
64
+ // The payments the tab and the chosen status leave.
65
+ shown: payments,
66
+ tab: 'all',
67
+ incoming: payments.filter((payment) => payment.direction === 'in').length,
68
+ outgoing: payments.filter((payment) => payment.direction === 'out').length,
69
+ statuses: ['All', 'Paid', 'Pending', 'Failed'],
70
+ status: 'All',
71
+ period: 'week',
72
+ };
@@ -0,0 +1,43 @@
1
+ <View>
2
+ <Text variant="title">Tabs and filters</Text>
3
+ <Text>Tabs switch what a page shows, chips narrow it down, and an empty state says when nothing is left.</Text>
4
+
5
+ <Tabs value="{{ tab }}" onChange="chooseTab">
6
+ <Tab value="all" label="All" />
7
+ <Tab value="in" label="Incoming" badge="{{ incoming }}" />
8
+ <Tab value="out" label="Outgoing" badge="{{ outgoing }}" />
9
+ </Tabs>
10
+
11
+ <View direction="row" gap="small" wrap>
12
+ <For each="{{ statuses }}" item="option" key="*this">
13
+ <Chip label="{{ option }}" selected="{{ option === status }}" data-status="{{ option }}" onTap="chooseStatus" />
14
+ </For>
15
+ </View>
16
+
17
+ <If condition="{{ shown.length > 0 }}">
18
+ <List>
19
+ <For each="{{ shown }}" item="payment" key="id">
20
+ <ListItem icon="{{ payment.direction === 'in' ? 'arrow-down' : 'arrow-up' }}" title="{{ payment.title }}" subtitle="{{ payment.date }} · {{ payment.amount }}">
21
+ <Badge tone="{{ payment.tone }}">{{ payment.status }}</Badge>
22
+ </ListItem>
23
+ </For>
24
+ </List>
25
+ </If>
26
+ <Else>
27
+ <EmptyState icon="search" title="No payments here" text="Nothing matches this tab and status.">
28
+ <Button variant="secondary" onClick="clearFilters">Clear filters</Button>
29
+ </EmptyState>
30
+ </Else>
31
+
32
+ <View gap="small">
33
+ <Text variant="caption" tone="muted">Segmented tabs, for a few short choices</Text>
34
+ <Tabs variant="segmented" value="{{ period }}" onChange="choosePeriod">
35
+ <Tab value="week" label="Week" />
36
+ <Tab value="month" label="Month" />
37
+ <Tab value="year" label="Year" />
38
+ </Tabs>
39
+ <View variant="card">
40
+ <Text>Spent this {{ period }}: {{ period === 'week' ? 'Le 95,000' : period === 'month' ? 'Le 410,000' : 'Le 4,870,000' }}</Text>
41
+ </View>
42
+ </View>
43
+ </View>
@@ -0,0 +1,44 @@
1
+ import state from './state';
2
+
3
+ type Typed = DotEvent<{ value: string }>;
4
+ type Toggled = DotEvent<{ checked: boolean }>;
5
+
6
+ export default definePage({
7
+ initialState: state,
8
+ mutations: {
9
+ setAccount: (_state, event: Typed) => ({ account: event.detail.value }),
10
+ setName: (_state, event: Typed) => ({ name: event.detail.value }),
11
+ setEmail: (_state, event: Typed) => ({ email: event.detail.value }),
12
+ setPhone: (_state, event: Typed) => ({ phone: event.detail.value }),
13
+ setAmount: (_state, event: Typed) => ({ amount: event.detail.value }),
14
+ setNote: (_state, event: Typed) => ({ note: event.detail.value }),
15
+ setPlan: (_state, event: Typed) => ({ plan: event.detail.value }),
16
+ setReceipts: (_state, event: Toggled) => ({ receipts: event.detail.checked }),
17
+ setAgreed: (_state, event: Toggled) => ({ agreed: event.detail.checked }),
18
+ },
19
+ actions: {
20
+ async send(state) {
21
+ const errors = {
22
+ phone: state.phone.length < 8 ? 'Enter a phone number, such as 076 123456' : '',
23
+ amount: Number(state.amount) >= 1000 ? '' : 'Send at least Le 1,000',
24
+ };
25
+ this.setState({ errors, sent: '', failure: '' });
26
+ if (errors.phone || errors.amount) {
27
+ return;
28
+ }
29
+ this.setState({ sending: true });
30
+ try {
31
+ const { account, name, email, phone, amount, note, plan, receipts } = state;
32
+ await this.dash.network.http.request({
33
+ url: 'https://httpbin.org/post',
34
+ method: 'POST',
35
+ body: { account, name, email, phone, amount: Number(amount), note, plan, receipts },
36
+ });
37
+ this.setState({ sent: `Le ${amount} to ${phone}` });
38
+ } catch (error) {
39
+ this.setState({ failure: (error as Error).message });
40
+ }
41
+ this.setState({ sending: false });
42
+ },
43
+ },
44
+ });
@@ -0,0 +1,15 @@
1
+ export default {
2
+ account: '',
3
+ name: '',
4
+ email: '',
5
+ phone: '',
6
+ amount: '',
7
+ note: '',
8
+ plan: 'monthly',
9
+ receipts: true,
10
+ agreed: false,
11
+ errors: { phone: '', amount: '' },
12
+ sending: false,
13
+ sent: '',
14
+ failure: '',
15
+ };