create-lupine 1.0.13 → 1.0.15

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 (42) hide show
  1. package/index.js +32 -2
  2. package/package.json +1 -1
  3. package/templates/common/AI_CONTEXT.md +245 -50
  4. package/templates/common/dev/cp-folder.js +2 -6
  5. package/templates/cv-starter/web/src/index.html +1 -1
  6. package/templates/doc-starter/web/src/index.html +1 -1
  7. package/templates/hello-world/web/src/index.html +1 -1
  8. package/templates/hello-world/web/src/index.tsx +54 -15
  9. package/templates/hello-world/web/src/styles/global.css +63 -0
  10. package/templates/responsive-starter/api/package.json +6 -0
  11. package/templates/responsive-starter/api/resources/config_default.json +6 -0
  12. package/templates/responsive-starter/api/resources/install.sqlite.sql +4 -0
  13. package/templates/responsive-starter/api/src/index.ts +4 -0
  14. package/templates/responsive-starter/api/src/service/root-api.ts +18 -0
  15. package/templates/responsive-starter/lupine.json +23 -0
  16. package/templates/responsive-starter/web/assets/favicon.ico +0 -0
  17. package/templates/responsive-starter/web/package.json +6 -0
  18. package/templates/responsive-starter/web/src/app-icons.ts +72 -0
  19. package/templates/responsive-starter/web/src/components/input-history-component.tsx +93 -0
  20. package/templates/responsive-starter/web/src/components/mine-about-page.tsx +86 -0
  21. package/templates/responsive-starter/web/src/components/mine-premium-page.tsx +80 -0
  22. package/templates/responsive-starter/web/src/components/mine-settings-page.tsx +219 -0
  23. package/templates/responsive-starter/web/src/components/mobile-top-search-menu.tsx +119 -0
  24. package/templates/responsive-starter/web/src/components/note-detail.tsx +116 -0
  25. package/templates/responsive-starter/web/src/components/note-edit.tsx +154 -0
  26. package/templates/responsive-starter/web/src/components/note-search-page.tsx +193 -0
  27. package/templates/responsive-starter/web/src/components/search-input.tsx +95 -0
  28. package/templates/responsive-starter/web/src/components/side-menu-content.tsx +178 -0
  29. package/templates/responsive-starter/web/src/frames/app-responsive-frame.tsx +46 -0
  30. package/templates/responsive-starter/web/src/index.html +16 -0
  31. package/templates/responsive-starter/web/src/index.tsx +42 -0
  32. package/templates/responsive-starter/web/src/pages/about-page.tsx +43 -0
  33. package/templates/responsive-starter/web/src/pages/finance-page.tsx +46 -0
  34. package/templates/responsive-starter/web/src/pages/home-page.tsx +452 -0
  35. package/templates/responsive-starter/web/src/pages/mine-page.tsx +325 -0
  36. package/templates/responsive-starter/web/src/pages/tools-page.tsx +46 -0
  37. package/templates/responsive-starter/web/src/services/local-notes-service.ts +87 -0
  38. package/templates/responsive-starter/web/src/services/mine-service.ts +45 -0
  39. package/templates/responsive-starter/web/src/styles/app.css +0 -0
  40. package/templates/responsive-starter/web/src/styles/base-css.ts +65 -0
  41. package/templates/responsive-starter/web/src/styles/global.css +2143 -0
  42. package/templates/responsive-starter/web/src/styles/theme.ts +16 -0
@@ -0,0 +1,193 @@
1
+ import { CssProps, HeaderWithBackFrame, HtmlVar, RefProps, SliderFrame, SliderFrameHookProps } from 'lupine.components';
2
+ import { SearchInput, SearchInputHookProps } from './search-input';
3
+ import { InputHistoryComponent, addHistoryItem } from './input-history-component';
4
+ import { LocalNoteProps, LocalNotesService } from '../services/local-notes-service';
5
+ import { NoteDetailComponent } from './note-detail';
6
+ import { clearHistoryList } from './input-history-component';
7
+
8
+ const extractText = (html: string) => {
9
+ const tmp = document.createElement('DIV');
10
+ tmp.innerHTML = html;
11
+ return tmp.textContent || tmp.innerText || '';
12
+ };
13
+
14
+ export const NoteSearchComponent = (props: { sliderFrameHook: SliderFrameHookProps }) => {
15
+ const listDom = new HtmlVar('');
16
+ const historyDom = new HtmlVar('');
17
+
18
+ const searchInputHook: SearchInputHookProps = {};
19
+ // this is the inner slide hook for editing from search results
20
+ const subSliderHook: SliderFrameHookProps = {};
21
+
22
+ const historyKey = 'note-search';
23
+
24
+ const onSearch = (value?: string) => {
25
+ const val = (value || '').trim();
26
+ if (val) {
27
+ addHistoryItem(historyKey, val);
28
+ hideHistory();
29
+ } else {
30
+ showHistory();
31
+ }
32
+ refreshList(val);
33
+ };
34
+
35
+ const onClearSearch = () => {
36
+ onSearch('');
37
+ };
38
+
39
+ const showHistory = () => {
40
+ historyDom.value = (
41
+ <InputHistoryComponent
42
+ historyKey={historyKey}
43
+ onClearHistory={() => {
44
+ clearHistoryList(historyKey);
45
+ showHistory();
46
+ }}
47
+ onItemClick={(item) => {
48
+ searchInputHook.setValue!(item);
49
+ onSearch(item);
50
+ }}
51
+ />
52
+ );
53
+ };
54
+
55
+ const hideHistory = () => {
56
+ historyDom.value = '';
57
+ };
58
+
59
+ const onViewNote = (note: LocalNoteProps) => {
60
+ subSliderHook.load!(
61
+ <NoteDetailComponent
62
+ note={note}
63
+ sliderFrameHook={subSliderHook}
64
+ onSaved={() => {
65
+ // We re-query the note values after saving. Easiest is to trigger search again.
66
+ const val = document.querySelector('.search-input .search-in-input') as HTMLInputElement;
67
+ if (val) onSearch(val.value);
68
+ }}
69
+ />
70
+ );
71
+ };
72
+
73
+ const refreshList = (query: string) => {
74
+ if (!query) {
75
+ listDom.value = '';
76
+ return;
77
+ }
78
+
79
+ const allNotes = LocalNotesService.getAllNotes();
80
+ const filteredNotes = allNotes.filter(
81
+ (n) =>
82
+ n.title.toLowerCase().includes(query.toLowerCase()) || n.content.toLowerCase().includes(query.toLowerCase())
83
+ );
84
+
85
+ if (filteredNotes.length === 0) {
86
+ listDom.value = (
87
+ <div
88
+ class='note-empty-state'
89
+ style={{ marginTop: '40px', textAlign: 'center', color: 'var(--secondary-color)' }}
90
+ >
91
+ No search results
92
+ </div>
93
+ );
94
+ return;
95
+ }
96
+
97
+ listDom.value = (
98
+ <div class='note-list-container' style={{ paddingTop: '16px' }}>
99
+ {filteredNotes.map((note) => (
100
+ <div class='note-card-wrapper search-result-card-wrapper'>
101
+ <div class='note-card row-box search-result-card' onClick={() => onViewNote(note)}>
102
+ <div class='note-card-content flex-1'>
103
+ <div class='note-card-title'>{note.title}</div>
104
+ <div class='note-card-preview ellipsis'>{extractText(note.content)}</div>
105
+ <div class='note-card-date'>{new Date(note.updatedAt).toLocaleString()}</div>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ ))}
110
+ </div>
111
+ );
112
+ };
113
+
114
+ const css: CssProps = {
115
+ height: '100%',
116
+ display: 'flex',
117
+ flexDirection: 'column',
118
+ overflowY: 'auto',
119
+ backgroundColor: 'var(--primary-bg-color)',
120
+ padding: '0 16px',
121
+
122
+ '.search-result-card-wrapper': {
123
+ marginBottom: '16px',
124
+ borderRadius: '12px',
125
+ overflow: 'hidden',
126
+ boxShadow: '0 4px 12px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.05)',
127
+ backgroundColor: 'var(--secondary-bg-color)',
128
+ },
129
+ '.search-result-card': {
130
+ backgroundColor: 'var(--primary-bg-color)',
131
+ borderRadius: '12px',
132
+ padding: '16px',
133
+ cursor: 'pointer',
134
+ alignItems: 'center',
135
+ transition: 'transform 0.2s ease, box-shadow 0.2s ease',
136
+ '&:active': {
137
+ transform: 'scale(0.98)',
138
+ },
139
+ },
140
+ '.note-card-title': {
141
+ fontSize: '16px',
142
+ fontWeight: 'bold',
143
+ color: 'var(--primary-color)',
144
+ marginBottom: '4px',
145
+ },
146
+ '.note-card-preview': {
147
+ fontSize: '14px',
148
+ color: 'var(--secondary-color)',
149
+ marginBottom: '6px',
150
+ },
151
+ '.note-card-date': {
152
+ fontSize: '12px',
153
+ color: 'var(--secondary-color)',
154
+ opacity: 0.8,
155
+ },
156
+ };
157
+
158
+ const ref: RefProps = {
159
+ onLoad: async () => {
160
+ showHistory();
161
+ // focus input
162
+ const input = ref.$('.search-in-input') as HTMLInputElement;
163
+ if (input) {
164
+ requestAnimationFrame(() => input.focus());
165
+ }
166
+ },
167
+ };
168
+
169
+ const headerTitle = (
170
+ <SearchInput
171
+ placeholder='Search title or content...'
172
+ onSearch={onSearch}
173
+ onClear={onClearSearch}
174
+ onFocus={showHistory}
175
+ class='w-100p'
176
+ hook={searchInputHook}
177
+ />
178
+ );
179
+
180
+ return (
181
+ <HeaderWithBackFrame
182
+ title={headerTitle}
183
+ right={<span></span>}
184
+ onBack={(event) => props.sliderFrameHook.close!(event)}
185
+ >
186
+ <div ref={ref} css={css} class='note-search-page'>
187
+ <SliderFrame hook={subSliderHook} />
188
+ {historyDom.node}
189
+ {listDom.node}
190
+ </div>
191
+ </HeaderWithBackFrame>
192
+ );
193
+ };
@@ -0,0 +1,95 @@
1
+ import { bindGlobalStyle, CssProps, RefProps } from 'lupine.components';
2
+
3
+ export interface SearchInputHookProps {
4
+ setValue?: (value: string) => void;
5
+ }
6
+
7
+ export interface InputWithClearProps {
8
+ placeholder?: string;
9
+ onSearch: (value: string) => void;
10
+ onClear?: () => void;
11
+ onFocus?: () => void;
12
+ onBlur?: () => void;
13
+ class?: string;
14
+ searchIcon?: string;
15
+ hook?: SearchInputHookProps;
16
+ }
17
+ export const SearchInput = (props: InputWithClearProps) => {
18
+ const css: CssProps = {
19
+ display: 'flex',
20
+ flex: 1,
21
+ position: 'relative',
22
+ fontSize: '16px',
23
+ '.search-in-input': {
24
+ flex: 1,
25
+ paddingRight: '30px',
26
+ height: '32px',
27
+ padding: '4px 30px 4px 12px',
28
+ borderRadius: '16px',
29
+ border: 'none',
30
+ backgroundColor: 'var(--secondary-bg-color)',
31
+ color: 'var(--primary-color)',
32
+ outline: 'none',
33
+ },
34
+ '.search-in-search, .search-in-clear': {
35
+ position: 'absolute',
36
+ top: '50%',
37
+ transform: 'translateY(-50%)',
38
+ cursor: 'pointer',
39
+ display: 'flex',
40
+ alignItems: 'center',
41
+ justifyContent: 'center',
42
+ color: 'var(--secondary-color)',
43
+ },
44
+ '.search-in-search': {
45
+ right: '12px',
46
+ },
47
+ '.search-in-clear': {
48
+ display: 'none',
49
+ right: '34px',
50
+ },
51
+ 'input.search-in-input:not(:placeholder-shown) + .search-in-clear': {
52
+ display: 'flex',
53
+ },
54
+ };
55
+ bindGlobalStyle('search-input-component', css);
56
+
57
+ const ref: RefProps = {};
58
+ const onSearch = () => {
59
+ const value = ref.$('.search-in-input').value;
60
+ props.onSearch(value);
61
+ };
62
+ const onClear = () => {
63
+ ref.$('.search-in-input').value = '';
64
+ props.onClear?.();
65
+ };
66
+ if (props.hook) {
67
+ props.hook.setValue = (value: string) => {
68
+ ref.$('input.search-in-input').value = value;
69
+ };
70
+ }
71
+ return (
72
+ <div ref={ref} class={'search-input-component ' + (props.class || '')}>
73
+ <input
74
+ type='text'
75
+ maxLength={30}
76
+ spellcheck={false}
77
+ class='search-in-input input-base'
78
+ placeholder={props.placeholder}
79
+ onKeyDown={(e) => {
80
+ if (e.key === 'Enter') {
81
+ onSearch();
82
+ }
83
+ }}
84
+ onBlur={() => props.onBlur?.()}
85
+ onFocus={() => props.onFocus?.()}
86
+ />
87
+ <div class='search-in-clear' onClick={onClear}>
88
+ <i class='ifc-icon ma-close' />
89
+ </div>
90
+ <div class='search-in-search' onClick={onSearch}>
91
+ <i class={'ifc-icon ' + (props.searchIcon || 'bs-search')} />
92
+ </div>
93
+ </div>
94
+ );
95
+ };
@@ -0,0 +1,178 @@
1
+ import {
2
+ CssProps,
3
+ getGlobalStylesId,
4
+ bindGlobalStyle,
5
+ RefProps,
6
+ VNode,
7
+ MobileSideMenuHelper,
8
+ updateTheme,
9
+ } from 'lupine.components';
10
+ import { NotificationColor, NotificationMessage, SliderFrameHookProps } from 'lupine.components';
11
+ import { IconMenuItemProps } from 'lupine.components';
12
+ import { MineSettingsPage } from './mine-settings-page';
13
+
14
+ export type SideMenuContentProps = {
15
+ navItems?: IconMenuItemProps[];
16
+ menuItems?: string[];
17
+ title?: string;
18
+ footer?: string;
19
+ version?: string;
20
+ onAction?: (action: string) => void;
21
+ settingSliderHook: SliderFrameHookProps;
22
+ };
23
+
24
+ const sideMenuContentCss: CssProps = {
25
+ margin: '0 -12px',
26
+ '.msm-header': {
27
+ display: 'flex',
28
+ alignItems: 'center',
29
+ padding: '16px',
30
+ borderBottom: '1px solid var(--primary-border-color)',
31
+ },
32
+ '.msm-header-icon': {
33
+ width: '32px',
34
+ height: '32px',
35
+ marginRight: '12px',
36
+ borderRadius: '4px',
37
+ backgroundColor: 'var(--primary-color)',
38
+ },
39
+ '.msm-header-title': {
40
+ fontSize: '18px',
41
+ fontWeight: 'bold',
42
+ color: 'var(--primary-color)',
43
+ },
44
+ '.msm-content': {
45
+ flex: '1',
46
+ overflowY: 'auto',
47
+ padding: '12px 0',
48
+ },
49
+ '.msm-item-link': {
50
+ textDecoration: 'none',
51
+ color: 'inherit',
52
+ display: 'block',
53
+ },
54
+ '.msm-separator': {
55
+ height: '1px',
56
+ backgroundColor: 'var(--primary-border-color)',
57
+ margin: '8px 12px',
58
+ },
59
+ '.msm-item': {
60
+ padding: '12px',
61
+ fontSize: '16px',
62
+ cursor: 'pointer',
63
+ transition: 'background-color 0.2s',
64
+ display: 'flex',
65
+ alignItems: 'center',
66
+ gap: '12px',
67
+ width: '100%',
68
+ '&:hover': {
69
+ backgroundColor: 'var(--activatable-bg-color-hover, rgba(0,0,0,0.04))',
70
+ },
71
+ },
72
+ '.msm-footer': {
73
+ borderTop: '1px solid var(--primary-border-color)',
74
+ padding: '16px 0',
75
+ },
76
+ '.msm-footer-cfg': {
77
+ display: 'flex',
78
+ flexDirection: 'row',
79
+ justifyContent: 'space-between',
80
+ },
81
+ '.msm-footer-cfg .msm-item': {
82
+ padding: '12px',
83
+ },
84
+ '.msm-footer-version': {
85
+ textAlign: 'center',
86
+ fontSize: '12px',
87
+ color: 'var(--secondary-color, #999)',
88
+ marginTop: '2px',
89
+ },
90
+ };
91
+
92
+ export const SideMenuContent = ({
93
+ navItems = [],
94
+ menuItems = ['Light Mode', 'Dark Mode'],
95
+ title = 'Function Menu',
96
+ footer,
97
+ onAction,
98
+ settingSliderHook,
99
+ }: SideMenuContentProps): VNode<any> => {
100
+ const globalCssId = getGlobalStylesId(sideMenuContentCss);
101
+ bindGlobalStyle(globalCssId, sideMenuContentCss);
102
+
103
+ const ref: RefProps = {
104
+ globalCssId,
105
+ };
106
+
107
+ const handleAction = (item: string) => {
108
+ if (onAction) {
109
+ onAction(item);
110
+ } else {
111
+ if (item === 'Light Mode') {
112
+ updateTheme('light');
113
+ MobileSideMenuHelper.hide();
114
+ } else if (item === 'Dark Mode') {
115
+ updateTheme('dark');
116
+ MobileSideMenuHelper.hide();
117
+ } else {
118
+ NotificationMessage.sendMessage(`Action: ${item}`, NotificationColor.Info);
119
+ MobileSideMenuHelper.hide();
120
+ }
121
+ }
122
+ };
123
+
124
+ return (
125
+ <div ref={ref} style={{ display: 'flex', flexDirection: 'column', flex: 1, height: '100%' }}>
126
+ <div class='msm-header'>
127
+ {/* <div class='msm-header-icon'></div> */}
128
+ <div class='msm-header-title'>{title}</div>
129
+ </div>
130
+
131
+ <div class='msm-content'>
132
+ {navItems &&
133
+ navItems.map((item, index) => {
134
+ const handleClick = (e: Event) => {
135
+ if (item.js) {
136
+ e.preventDefault();
137
+ item.js();
138
+ }
139
+ MobileSideMenuHelper.hide();
140
+ };
141
+
142
+ return (
143
+ <a href={item.url || 'javascript:void(0)'} class='msm-item-link' onClick={handleClick}>
144
+ <div class='msm-item'>
145
+ {item.icon && <i class={`ifc-icon ${item.icon}`}></i>}
146
+ {item.text}
147
+ </div>
148
+ </a>
149
+ );
150
+ })}
151
+ {navItems && navItems.length > 0 && menuItems.length > 0 && <div class='msm-separator'></div>}
152
+ {menuItems.map((item) => (
153
+ <div class='msm-item' onClick={() => handleAction(item)}>
154
+ {item}
155
+ </div>
156
+ ))}
157
+ </div>
158
+
159
+ <div class='msm-footer'>
160
+ <div class='msm-footer-cfg'>
161
+ <div
162
+ class='msm-item'
163
+ onClick={() => {
164
+ settingSliderHook.load!(
165
+ <MineSettingsPage sliderFrameHook={settingSliderHook} onDataChanged={() => {}} />
166
+ );
167
+ MobileSideMenuHelper.hide();
168
+ }}
169
+ >
170
+ <i class='ifc-icon ma-cog-outline'></i>
171
+ Settings
172
+ </div>
173
+ </div>
174
+ {footer && <div class='msm-footer-version'>{footer}</div>}
175
+ </div>
176
+ </div>
177
+ );
178
+ };
@@ -0,0 +1,46 @@
1
+ import { VNode } from 'lupine.web';
2
+ import { MediaQueryMaxWidth, ResponsiveFrame, SliderFrame, SliderFrameHookProps } from 'lupine.components';
3
+ import { SideMenuContent } from '../components/side-menu-content';
4
+
5
+ // Note: Replace with true site config loading if available
6
+ const getSiteTitle = async () => 'My Note App';
7
+ const getSiteFooter = async () => '© 2026 My Note App';
8
+
9
+ // 'sidemenu' | 'tabs', this will be replaced by create-lupine
10
+ const DEFAULT_LAYOUT = 'tabs';
11
+ export const AppResponsiveFrame = async (placeholderClassname: string, vnode: VNode<any>) => {
12
+ const mobileBottomMenu = [
13
+ { icon: 'ma-home-outline', url: '/', text: 'Notes' },
14
+ { icon: 'icon-finance', url: '/finance', text: 'Finance' },
15
+ { icon: 'ma-book-outline', url: '/about', text: 'About', topout: true },
16
+ { icon: 'ma-tools', url: '/tools', text: 'Tools' },
17
+ { icon: 'ma-account-cog-outline', url: '/mine', text: 'Mine' },
18
+ ];
19
+
20
+ const layout: 'sidemenu' | 'tabs' = (localStorage.getItem('app-layout') as any) || DEFAULT_LAYOUT;
21
+ const sliderFrameHook: SliderFrameHookProps = {};
22
+
23
+ return ResponsiveFrame({
24
+ placeholderClassname,
25
+ mainContent: vnode,
26
+ desktopHeaderTitle: await getSiteTitle(),
27
+ desktopFooterTitle: await getSiteFooter(),
28
+ desktopTopMenu: layout === 'sidemenu' ? undefined : mobileBottomMenu,
29
+ mobileBottomMenu: layout === 'sidemenu' ? undefined : mobileBottomMenu,
30
+ sharedContents: (
31
+ <>
32
+ <SliderFrame hook={sliderFrameHook} />
33
+ </>
34
+ ),
35
+ mobileSideMenuContent: (
36
+ <SideMenuContent
37
+ navItems={layout === 'sidemenu' ? mobileBottomMenu : undefined}
38
+ settingSliderHook={sliderFrameHook}
39
+ title='My Note App'
40
+ footer='Powered by Lupine.js'
41
+ />
42
+ ),
43
+ maxWidth: MediaQueryMaxWidth.DesktopMax,
44
+ autoExtendSidemenu: layout === 'sidemenu',
45
+ });
46
+ };
@@ -0,0 +1,16 @@
1
+ <!DOCTYPE html>
2
+ <html data-theme="<!--META-THEME-->">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title><!--META-TITLE--></title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=, user-scalable=no" />
7
+ <!--META-ENV-START-->
8
+ <!--META-ENV-END-->
9
+ <link rel="shortcut icon" href="{SUBDIR}/assets/favicon.ico?t={hash}" type="image/x-icon" />
10
+ <link rel="stylesheet" type="text/css" href="{SUBDIR}/index.css?t={hash}" />
11
+ <script defer src="{SUBDIR}/index.js#t={hash}"></script>
12
+ </head>
13
+ <body>
14
+ <div class="lupine-root"></div>
15
+ </body>
16
+ </html>
@@ -0,0 +1,42 @@
1
+ // css order is important
2
+ import './styles/global.css';
3
+ import './styles/app.css';
4
+
5
+ import {
6
+ bindRouter,
7
+ bindGlobalStyle,
8
+ debugWatch,
9
+ isFrontEnd,
10
+ PageRouter,
11
+ webEnv,
12
+ bindLang,
13
+ bindTheme,
14
+ setDefaultPageTitle,
15
+ } from 'lupine.components';
16
+ import { baseCss } from './styles/base-css';
17
+ import { themes } from './styles/theme';
18
+ import { AppResponsiveFrame } from './frames/app-responsive-frame';
19
+ import { HomePage } from './pages/home-page';
20
+ import { FinancePage } from './pages/finance-page';
21
+ import { AboutPage } from './pages/about-page';
22
+ import { MinePage } from './pages/mine-page';
23
+ import { ToolsPage } from './pages/tools-page';
24
+
25
+ bindLang('zh-cn', {});
26
+ bindTheme('light', themes);
27
+ bindGlobalStyle('comm-css', baseCss, false, true);
28
+ setDefaultPageTitle('Lupine.js Note Starter');
29
+
30
+ if (isFrontEnd() && webEnv('NODE_ENV', '') === 'development') {
31
+ debugWatch(webEnv('API_PORT', 0));
32
+ }
33
+
34
+ const pageRouter = new PageRouter();
35
+ pageRouter.setFramePage({ component: AppResponsiveFrame, placeholderClassname: 'user-page-placeholder' });
36
+ pageRouter.use('/finance', FinancePage);
37
+ pageRouter.use('/about', AboutPage);
38
+ pageRouter.use('/mine', MinePage);
39
+ pageRouter.use('/tools', ToolsPage);
40
+ pageRouter.use('*', HomePage);
41
+
42
+ bindRouter(pageRouter);
@@ -0,0 +1,43 @@
1
+ import {
2
+ CssProps,
3
+ MobileHeaderCenter,
4
+ MobileHeaderEmptyIcon,
5
+ MobileHeaderTitleIcon,
6
+ MobileTopSysIcon,
7
+ PageProps,
8
+ RefProps,
9
+ } from 'lupine.components';
10
+ import { MineAboutContent } from '../components/mine-about-page';
11
+
12
+ export const AboutPage = async (props: PageProps) => {
13
+ const css: CssProps = {
14
+ display: 'flex',
15
+ flexDirection: 'column',
16
+ height: '100%',
17
+ position: 'relative',
18
+ };
19
+
20
+ const ref: RefProps = {
21
+ onLoad: async () => {},
22
+ };
23
+
24
+ return (
25
+ <div css={css} ref={ref}>
26
+ <MobileHeaderCenter>
27
+ <MobileHeaderTitleIcon
28
+ title='About'
29
+ left={<MobileHeaderEmptyIcon />}
30
+ right={
31
+ <div class='flex-center-gap-12'>
32
+ <MobileTopSysIcon />
33
+ </div>
34
+ }
35
+ />
36
+ </MobileHeaderCenter>
37
+
38
+ <div class='tools-scroll tools-list-container no-scrollbar-container'>
39
+ <MineAboutContent />
40
+ </div>
41
+ </div>
42
+ );
43
+ };
@@ -0,0 +1,46 @@
1
+ import {
2
+ CssProps,
3
+ MobileHeaderCenter,
4
+ MobileHeaderEmptyIcon,
5
+ MobileHeaderTitleIcon,
6
+ MobileTopSysIcon,
7
+ PageProps,
8
+ RefProps,
9
+ } from 'lupine.components';
10
+
11
+ export const FinancePage = async (props: PageProps) => {
12
+ const css: CssProps = {
13
+ display: 'flex',
14
+ flexDirection: 'column',
15
+ height: '100%',
16
+ position: 'relative',
17
+ '.finance-list-container': {
18
+ padding: '32px 24px',
19
+ display: 'flex',
20
+ flexDirection: 'column',
21
+ alignItems: 'center',
22
+ },
23
+ };
24
+
25
+ const ref: RefProps = {
26
+ onLoad: async () => {},
27
+ };
28
+
29
+ return (
30
+ <div css={css} ref={ref}>
31
+ <MobileHeaderCenter>
32
+ <MobileHeaderTitleIcon
33
+ title='Finance'
34
+ left={<MobileHeaderEmptyIcon />}
35
+ right={
36
+ <div class='flex-center-gap-12'>
37
+ <MobileTopSysIcon />
38
+ </div>
39
+ }
40
+ />
41
+ </MobileHeaderCenter>
42
+
43
+ <div class='finance-list-container no-scrollbar-container'>This is a finance demo page.</div>
44
+ </div>
45
+ );
46
+ };