@robsun/create-keystone-app 0.1.18 → 0.2.1

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 (45) hide show
  1. package/README.md +18 -5
  2. package/{bin → dist}/create-keystone-app.js +114 -116
  3. package/dist/create-module.js +638 -0
  4. package/package.json +11 -7
  5. package/template/README.md +17 -13
  6. package/template/apps/server/config.example.yaml +0 -1
  7. package/template/apps/server/config.yaml +0 -1
  8. package/template/apps/server/internal/modules/example/api/handler/item_handler.go +162 -0
  9. package/template/apps/server/internal/modules/example/bootstrap/migrations/item.go +21 -0
  10. package/template/apps/server/internal/modules/example/bootstrap/seeds/item.go +33 -0
  11. package/template/apps/server/internal/modules/example/domain/models/item.go +30 -0
  12. package/template/apps/server/internal/modules/{demo → example}/domain/service/errors.go +1 -1
  13. package/template/apps/server/internal/modules/example/domain/service/item_service.go +110 -0
  14. package/template/apps/server/internal/modules/example/infra/repository/item_repository.go +49 -0
  15. package/template/apps/server/internal/modules/example/module.go +55 -17
  16. package/template/apps/server/internal/modules/manifest.go +0 -2
  17. package/template/apps/web/src/app.config.ts +1 -1
  18. package/template/apps/web/src/main.tsx +1 -3
  19. package/template/apps/web/src/modules/example/help/faq.md +23 -0
  20. package/template/apps/web/src/modules/example/help/items.md +26 -0
  21. package/template/apps/web/src/modules/example/help/overview.md +18 -4
  22. package/template/apps/web/src/modules/example/pages/ExampleItemsPage.tsx +227 -0
  23. package/template/apps/web/src/modules/example/routes.tsx +33 -10
  24. package/template/apps/web/src/modules/example/services/exampleItems.ts +32 -0
  25. package/template/apps/web/src/modules/example/types.ts +10 -0
  26. package/template/docs/CONVENTIONS.md +44 -0
  27. package/template/docs/GETTING_STARTED.md +54 -0
  28. package/template/package.json +2 -1
  29. package/template/scripts/check-modules.js +7 -1
  30. package/template/apps/server/internal/modules/demo/api/handler/task_handler.go +0 -152
  31. package/template/apps/server/internal/modules/demo/bootstrap/migrations/task.go +0 -21
  32. package/template/apps/server/internal/modules/demo/bootstrap/seeds/task.go +0 -33
  33. package/template/apps/server/internal/modules/demo/domain/models/task.go +0 -30
  34. package/template/apps/server/internal/modules/demo/domain/service/task_service.go +0 -95
  35. package/template/apps/server/internal/modules/demo/infra/repository/task_repository.go +0 -49
  36. package/template/apps/server/internal/modules/demo/module.go +0 -91
  37. package/template/apps/server/internal/modules/example/handlers.go +0 -19
  38. package/template/apps/web/src/modules/demo/help/overview.md +0 -12
  39. package/template/apps/web/src/modules/demo/index.ts +0 -7
  40. package/template/apps/web/src/modules/demo/pages/DemoTasksPage.tsx +0 -185
  41. package/template/apps/web/src/modules/demo/routes.tsx +0 -43
  42. package/template/apps/web/src/modules/demo/services/demoTasks.ts +0 -28
  43. package/template/apps/web/src/modules/demo/types.ts +0 -9
  44. package/template/apps/web/src/modules/example/pages/ExamplePage.tsx +0 -41
  45. package/template/apps/web/src/modules/example/services/api.ts +0 -8
@@ -1,33 +0,0 @@
1
- package seeds
2
-
3
- import (
4
- "log"
5
-
6
- "gorm.io/gorm"
7
-
8
- "__APP_NAME__/apps/server/internal/modules/demo/domain/models"
9
- )
10
-
11
- func Seed(db *gorm.DB) error {
12
- if db == nil {
13
- return nil
14
- }
15
-
16
- var count int64
17
- if err := db.Model(&models.DemoTask{}).Count(&count).Error; err != nil {
18
- return err
19
- }
20
- if count > 0 {
21
- return nil
22
- }
23
-
24
- log.Println("[demo] Seeding initial data...")
25
- tasks := []models.DemoTask{
26
- {Title: "Set up project", Status: models.StatusTodo},
27
- {Title: "Review UI states", Status: models.StatusDone},
28
- }
29
- for i := range tasks {
30
- tasks[i].TenantID = 1
31
- }
32
- return db.Create(&tasks).Error
33
- }
@@ -1,30 +0,0 @@
1
- package models
2
-
3
- import "github.com/robsuncn/keystone/domain/models"
4
-
5
- type TaskStatus string
6
-
7
- const (
8
- StatusTodo TaskStatus = "todo"
9
- StatusInProgress TaskStatus = "in_progress"
10
- StatusDone TaskStatus = "done"
11
- )
12
-
13
- func (s TaskStatus) IsValid() bool {
14
- switch s {
15
- case StatusTodo, StatusInProgress, StatusDone:
16
- return true
17
- default:
18
- return false
19
- }
20
- }
21
-
22
- type DemoTask struct {
23
- models.BaseModel
24
- Title string `gorm:"size:200;not null" json:"title"`
25
- Status TaskStatus `gorm:"size:20;not null;default:'todo'" json:"status"`
26
- }
27
-
28
- func (DemoTask) TableName() string {
29
- return "demo_tasks"
30
- }
@@ -1,95 +0,0 @@
1
- package service
2
-
3
- import (
4
- "context"
5
- "strings"
6
-
7
- "__APP_NAME__/apps/server/internal/modules/demo/domain/models"
8
- )
9
-
10
- type TaskRepository interface {
11
- List(ctx context.Context, tenantID uint) ([]models.DemoTask, error)
12
- FindByID(tenantID, id uint) (*models.DemoTask, error)
13
- Create(ctx context.Context, task *models.DemoTask) error
14
- Update(ctx context.Context, task *models.DemoTask) error
15
- Delete(ctx context.Context, task *models.DemoTask) error
16
- }
17
-
18
- type TaskService struct {
19
- tasks TaskRepository
20
- }
21
-
22
- type TaskInput struct {
23
- Title string
24
- Status models.TaskStatus
25
- }
26
-
27
- type TaskUpdateInput struct {
28
- Title string
29
- Status models.TaskStatus
30
- }
31
-
32
- func NewTaskService(tasks TaskRepository) *TaskService {
33
- return &TaskService{tasks: tasks}
34
- }
35
-
36
- func (s *TaskService) List(ctx context.Context, tenantID uint) ([]models.DemoTask, error) {
37
- return s.tasks.List(ctx, tenantID)
38
- }
39
-
40
- func (s *TaskService) Create(ctx context.Context, tenantID uint, input TaskInput) (*models.DemoTask, error) {
41
- title := strings.TrimSpace(input.Title)
42
- if title == "" {
43
- return nil, ErrTitleRequired
44
- }
45
-
46
- status := input.Status
47
- if status == "" {
48
- status = models.StatusTodo
49
- }
50
- if !status.IsValid() {
51
- return nil, ErrStatusInvalid
52
- }
53
-
54
- task := &models.DemoTask{
55
- Title: title,
56
- Status: status,
57
- }
58
- task.TenantID = tenantID
59
-
60
- if err := s.tasks.Create(ctx, task); err != nil {
61
- return nil, err
62
- }
63
- return task, nil
64
- }
65
-
66
- func (s *TaskService) Update(ctx context.Context, tenantID, id uint, input TaskUpdateInput) (*models.DemoTask, error) {
67
- task, err := s.tasks.FindByID(tenantID, id)
68
- if err != nil {
69
- return nil, err
70
- }
71
-
72
- if title := strings.TrimSpace(input.Title); title != "" {
73
- task.Title = title
74
- }
75
-
76
- if input.Status != "" {
77
- if !input.Status.IsValid() {
78
- return nil, ErrStatusInvalid
79
- }
80
- task.Status = input.Status
81
- }
82
-
83
- if err := s.tasks.Update(ctx, task); err != nil {
84
- return nil, err
85
- }
86
- return task, nil
87
- }
88
-
89
- func (s *TaskService) Delete(ctx context.Context, tenantID, id uint) error {
90
- task, err := s.tasks.FindByID(tenantID, id)
91
- if err != nil {
92
- return err
93
- }
94
- return s.tasks.Delete(ctx, task)
95
- }
@@ -1,49 +0,0 @@
1
- package repository
2
-
3
- import (
4
- "context"
5
- "errors"
6
-
7
- "gorm.io/gorm"
8
-
9
- "__APP_NAME__/apps/server/internal/modules/demo/domain/models"
10
- "__APP_NAME__/apps/server/internal/modules/demo/domain/service"
11
- )
12
-
13
- type TaskRepository struct {
14
- db *gorm.DB
15
- }
16
-
17
- func NewTaskRepository(db *gorm.DB) *TaskRepository {
18
- return &TaskRepository{db: db}
19
- }
20
-
21
- func (r *TaskRepository) List(ctx context.Context, tenantID uint) ([]models.DemoTask, error) {
22
- var tasks []models.DemoTask
23
- err := r.db.WithContext(ctx).
24
- Where("tenant_id = ?", tenantID).
25
- Order("created_at desc").
26
- Find(&tasks).Error
27
- return tasks, err
28
- }
29
-
30
- func (r *TaskRepository) FindByID(tenantID, id uint) (*models.DemoTask, error) {
31
- var task models.DemoTask
32
- err := r.db.Where("tenant_id = ? AND id = ?", tenantID, id).First(&task).Error
33
- if errors.Is(err, gorm.ErrRecordNotFound) {
34
- return nil, service.ErrTaskNotFound
35
- }
36
- return &task, err
37
- }
38
-
39
- func (r *TaskRepository) Create(ctx context.Context, task *models.DemoTask) error {
40
- return r.db.WithContext(ctx).Create(task).Error
41
- }
42
-
43
- func (r *TaskRepository) Update(ctx context.Context, task *models.DemoTask) error {
44
- return r.db.WithContext(ctx).Save(task).Error
45
- }
46
-
47
- func (r *TaskRepository) Delete(ctx context.Context, task *models.DemoTask) error {
48
- return r.db.WithContext(ctx).Delete(task).Error
49
- }
@@ -1,91 +0,0 @@
1
- package demo
2
-
3
- import (
4
- "github.com/gin-gonic/gin"
5
- "gorm.io/gorm"
6
-
7
- "github.com/robsuncn/keystone/domain/permissions"
8
- "github.com/robsuncn/keystone/infra/jobs"
9
-
10
- demohandler "__APP_NAME__/apps/server/internal/modules/demo/api/handler"
11
- demomigrations "__APP_NAME__/apps/server/internal/modules/demo/bootstrap/migrations"
12
- demoseeds "__APP_NAME__/apps/server/internal/modules/demo/bootstrap/seeds"
13
- demomodels "__APP_NAME__/apps/server/internal/modules/demo/domain/models"
14
- demoservice "__APP_NAME__/apps/server/internal/modules/demo/domain/service"
15
- demorepository "__APP_NAME__/apps/server/internal/modules/demo/infra/repository"
16
- )
17
-
18
- type Module struct {
19
- tasks *demoservice.TaskService
20
- }
21
-
22
- func NewModule() *Module {
23
- return &Module{}
24
- }
25
-
26
- func (m *Module) Name() string {
27
- return "demo"
28
- }
29
-
30
- func (m *Module) RegisterRoutes(rg *gin.RouterGroup) {
31
- if rg == nil || m == nil {
32
- return
33
- }
34
- handler := demohandler.NewTaskHandler(m.tasks)
35
- if handler == nil {
36
- return
37
- }
38
- group := rg.Group("/demo")
39
- group.GET("/tasks", handler.List)
40
- group.POST("/tasks", handler.Create)
41
- group.PATCH("/tasks/:id", handler.Update)
42
- group.DELETE("/tasks/:id", handler.Delete)
43
- }
44
-
45
- func (m *Module) RegisterModels() []interface{} {
46
- return []interface{}{&demomodels.DemoTask{}}
47
- }
48
-
49
- func (m *Module) RegisterPermissions(reg *permissions.Registry) error {
50
- if reg == nil {
51
- return nil
52
- }
53
- if err := reg.CreateMenu("demo:task", "Demo Tasks", "demo", 10); err != nil {
54
- return err
55
- }
56
- if err := reg.CreateAction("demo:task:view", "View Tasks", "demo", "demo:task"); err != nil {
57
- return err
58
- }
59
- if err := reg.CreateAction("demo:task:manage", "Manage Tasks", "demo", "demo:task"); err != nil {
60
- return err
61
- }
62
- return nil
63
- }
64
-
65
- func (m *Module) RegisterJobs(_ *jobs.Registry) error {
66
- return nil
67
- }
68
-
69
- func (m *Module) Migrate(db *gorm.DB) error {
70
- if db == nil {
71
- return nil
72
- }
73
- m.ensureServices(db)
74
- return demomigrations.Migrate(db)
75
- }
76
-
77
- func (m *Module) Seed(db *gorm.DB) error {
78
- if db == nil {
79
- return nil
80
- }
81
- m.ensureServices(db)
82
- return demoseeds.Seed(db)
83
- }
84
-
85
- func (m *Module) ensureServices(db *gorm.DB) {
86
- if m == nil || db == nil || m.tasks != nil {
87
- return
88
- }
89
- repo := demorepository.NewTaskRepository(db)
90
- m.tasks = demoservice.NewTaskService(repo)
91
- }
@@ -1,19 +0,0 @@
1
- package example
2
-
3
- import (
4
- "github.com/gin-gonic/gin"
5
-
6
- "github.com/robsuncn/keystone/api/response"
7
- )
8
-
9
- type HelloResponse struct {
10
- Message string `json:"message"`
11
- Module string `json:"module"`
12
- }
13
-
14
- func handleHello(c *gin.Context) {
15
- response.Success(c, HelloResponse{
16
- Message: "Hello from Example module!",
17
- Module: "example",
18
- })
19
- }
@@ -1,12 +0,0 @@
1
- ---
2
- helpKey: "demo/tasks"
3
- title: "Demo Tasks"
4
- description: "Manage sample tasks in the scaffold."
5
- category: "demo"
6
- tags: ["demo", "tasks"]
7
- ---
8
-
9
- # Demo Tasks
10
-
11
- Use this module as a reference for list + create + update flows. Replace it
12
- with real business logic when you build new modules.
@@ -1,7 +0,0 @@
1
- import { registerModule } from '@robsun/keystone-web-core'
2
- import { demoRoutes } from './routes'
3
-
4
- registerModule({
5
- name: 'demo',
6
- routes: demoRoutes,
7
- })
@@ -1,185 +0,0 @@
1
- import { useCallback, useEffect, useMemo, useState } from 'react'
2
- import { App, Button, Card, Form, Input, Popconfirm, Select, Space, Table, Tag } from 'antd'
3
- import type { ColumnsType } from 'antd/es/table'
4
- import dayjs from 'dayjs'
5
- import { createDemoTask, deleteDemoTask, listDemoTasks, updateDemoTask } from '../services/demoTasks'
6
- import type { DemoTask, DemoTaskStatus } from '../types'
7
-
8
- type CreateValues = {
9
- title: string
10
- status?: DemoTaskStatus
11
- }
12
-
13
- const statusMeta: Record<DemoTaskStatus, { label: string; color: string }> = {
14
- todo: { label: 'Todo', color: 'default' },
15
- in_progress: { label: 'In Progress', color: 'processing' },
16
- done: { label: 'Done', color: 'success' },
17
- }
18
-
19
- const statusOptions = [
20
- { value: 'todo', label: 'Todo' },
21
- { value: 'in_progress', label: 'In Progress' },
22
- { value: 'done', label: 'Done' },
23
- ]
24
-
25
- export function DemoTasksPage() {
26
- const { message } = App.useApp()
27
- const [items, setItems] = useState<DemoTask[]>([])
28
- const [loading, setLoading] = useState(false)
29
- const [submitting, setSubmitting] = useState(false)
30
- const [form] = Form.useForm<CreateValues>()
31
-
32
- const fetchTasks = useCallback(async () => {
33
- setLoading(true)
34
- try {
35
- const data = await listDemoTasks()
36
- setItems(data)
37
- } catch (err) {
38
- const detail = err instanceof Error ? err.message : 'Failed to load tasks'
39
- message.error(detail)
40
- } finally {
41
- setLoading(false)
42
- }
43
- }, [message])
44
-
45
- useEffect(() => {
46
- void fetchTasks()
47
- }, [fetchTasks])
48
-
49
- const handleCreate = useCallback(
50
- async (values: CreateValues) => {
51
- const title = values.title?.trim()
52
- if (!title) {
53
- message.error('Title is required')
54
- return
55
- }
56
- setSubmitting(true)
57
- try {
58
- await createDemoTask({ title, status: values.status })
59
- form.resetFields()
60
- await fetchTasks()
61
- message.success('Task created')
62
- } catch (err) {
63
- const detail = err instanceof Error ? err.message : 'Failed to create task'
64
- message.error(detail)
65
- } finally {
66
- setSubmitting(false)
67
- }
68
- },
69
- [fetchTasks, form, message]
70
- )
71
-
72
- const handleStatusChange = useCallback(
73
- async (id: number, status: DemoTaskStatus) => {
74
- try {
75
- await updateDemoTask(id, { status })
76
- await fetchTasks()
77
- message.success('Task updated')
78
- } catch (err) {
79
- const detail = err instanceof Error ? err.message : 'Failed to update task'
80
- message.error(detail)
81
- }
82
- },
83
- [fetchTasks, message]
84
- )
85
-
86
- const handleDelete = useCallback(
87
- async (id: number) => {
88
- try {
89
- await deleteDemoTask(id)
90
- await fetchTasks()
91
- message.success('Task deleted')
92
- } catch (err) {
93
- const detail = err instanceof Error ? err.message : 'Failed to delete task'
94
- message.error(detail)
95
- }
96
- },
97
- [fetchTasks, message]
98
- )
99
-
100
- const columns: ColumnsType<DemoTask> = useMemo(
101
- () => [
102
- { title: 'Title', dataIndex: 'title', key: 'title' },
103
- {
104
- title: 'Status',
105
- dataIndex: 'status',
106
- key: 'status',
107
- render: (value: DemoTaskStatus) => {
108
- const meta = statusMeta[value]
109
- return <Tag color={meta.color}>{meta.label}</Tag>
110
- },
111
- },
112
- {
113
- title: 'Updated',
114
- dataIndex: 'updated_at',
115
- key: 'updated_at',
116
- render: (value: string) => (value ? dayjs(value).format('YYYY-MM-DD HH:mm') : '-'),
117
- },
118
- {
119
- title: 'Actions',
120
- key: 'actions',
121
- render: (_, record) => (
122
- <Space>
123
- <Select
124
- size="small"
125
- value={record.status}
126
- options={statusOptions}
127
- style={{ width: 140 }}
128
- onChange={(value) => handleStatusChange(record.id, value as DemoTaskStatus)}
129
- />
130
- <Popconfirm
131
- title="Delete this task?"
132
- onConfirm={() => handleDelete(record.id)}
133
- okText="Delete"
134
- >
135
- <Button size="small" danger>
136
- Delete
137
- </Button>
138
- </Popconfirm>
139
- </Space>
140
- ),
141
- },
142
- ],
143
- [handleDelete, handleStatusChange]
144
- )
145
-
146
- return (
147
- <Card
148
- title="Demo Tasks"
149
- extra={
150
- <Button onClick={fetchTasks} loading={loading}>
151
- Refresh
152
- </Button>
153
- }
154
- >
155
- <Space direction="vertical" size="middle" style={{ width: '100%' }}>
156
- <Form
157
- form={form}
158
- layout="inline"
159
- onFinish={handleCreate}
160
- initialValues={{ status: 'todo' }}
161
- >
162
- <Form.Item name="title" rules={[{ required: true, message: 'Title is required' }]}>
163
- <Input placeholder="Task title" allowClear style={{ width: 240 }} />
164
- </Form.Item>
165
- <Form.Item name="status">
166
- <Select options={statusOptions} style={{ width: 160 }} />
167
- </Form.Item>
168
- <Form.Item>
169
- <Button type="primary" htmlType="submit" loading={submitting}>
170
- Add Task
171
- </Button>
172
- </Form.Item>
173
- </Form>
174
-
175
- <Table<DemoTask>
176
- rowKey="id"
177
- loading={loading}
178
- columns={columns}
179
- dataSource={items}
180
- pagination={false}
181
- />
182
- </Space>
183
- </Card>
184
- )
185
- }
@@ -1,43 +0,0 @@
1
- import { lazy, Suspense, type ComponentType, type ReactElement } from 'react'
2
- import type { RouteObject } from 'react-router-dom'
3
- import { AppstoreOutlined } from '@ant-design/icons'
4
- import { Spin } from 'antd'
5
-
6
- const lazyNamed = <T extends Record<string, ComponentType>, K extends keyof T>(
7
- factory: () => Promise<T>,
8
- name: K
9
- ) =>
10
- lazy(async () => {
11
- const module = await factory()
12
- return { default: module[name] }
13
- })
14
-
15
- const withSuspense = (element: ReactElement) => (
16
- <Suspense
17
- fallback={
18
- <div style={{ padding: 24, display: 'flex', justifyContent: 'center' }}>
19
- <Spin />
20
- </div>
21
- }
22
- >
23
- {element}
24
- </Suspense>
25
- )
26
-
27
- const DemoTasksPage = lazyNamed(() => import('./pages/DemoTasksPage'), 'DemoTasksPage')
28
-
29
- export const demoRoutes: RouteObject[] = [
30
- {
31
- path: 'demo',
32
- element: <DemoTasksPage />,
33
- handle: {
34
- menu: { label: 'Demo Tasks', icon: <AppstoreOutlined /> },
35
- breadcrumb: 'Demo Tasks',
36
- permission: 'demo:task:view',
37
- helpKey: 'demo/tasks',
38
- },
39
- },
40
- ].map((route) => ({
41
- ...route,
42
- element: route.element ? withSuspense(route.element) : route.element,
43
- }))
@@ -1,28 +0,0 @@
1
- import { api, type ApiResponse } from '@robsun/keystone-web-core'
2
- import type { DemoTask, DemoTaskStatus } from '../types'
3
-
4
- type TaskListResponse = {
5
- items: DemoTask[]
6
- }
7
-
8
- export const listDemoTasks = async () => {
9
- const { data } = await api.get<ApiResponse<TaskListResponse>>('/demo/tasks')
10
- return data.data.items
11
- }
12
-
13
- export const createDemoTask = async (payload: { title: string; status?: DemoTaskStatus }) => {
14
- const { data } = await api.post<ApiResponse<DemoTask>>('/demo/tasks', payload)
15
- return data.data
16
- }
17
-
18
- export const updateDemoTask = async (
19
- id: number,
20
- payload: { title?: string; status?: DemoTaskStatus }
21
- ) => {
22
- const { data } = await api.patch<ApiResponse<DemoTask>>(`/demo/tasks/${id}`, payload)
23
- return data.data
24
- }
25
-
26
- export const deleteDemoTask = async (id: number) => {
27
- await api.delete<ApiResponse<{ id: number }>>(`/demo/tasks/${id}`)
28
- }
@@ -1,9 +0,0 @@
1
- export type DemoTaskStatus = 'todo' | 'in_progress' | 'done'
2
-
3
- export interface DemoTask {
4
- id: number
5
- title: string
6
- status: DemoTaskStatus
7
- created_at: string
8
- updated_at: string
9
- }
@@ -1,41 +0,0 @@
1
- import { useCallback, useEffect, useState } from 'react'
2
- import { App, Button, Card, Space, Typography } from 'antd'
3
- import { getHello } from '../services/api'
4
-
5
- export function ExamplePage() {
6
- const { message } = App.useApp()
7
- const [data, setData] = useState<{ message: string; module: string } | null>(null)
8
- const [loading, setLoading] = useState(false)
9
-
10
- const fetchData = useCallback(async () => {
11
- setLoading(true)
12
- try {
13
- setData(await getHello())
14
- } catch (err) {
15
- message.error(err instanceof Error ? err.message : 'Failed')
16
- } finally {
17
- setLoading(false)
18
- }
19
- }, [message])
20
-
21
- useEffect(() => {
22
- void fetchData()
23
- }, [fetchData])
24
-
25
- return (
26
- <Card title="Example Module">
27
- <Space direction="vertical">
28
- <Typography.Title level={4}>Welcome!</Typography.Title>
29
- <Typography.Text>This module demonstrates core Keystone patterns.</Typography.Text>
30
- {data && (
31
- <Card size="small">
32
- <pre>{JSON.stringify(data, null, 2)}</pre>
33
- </Card>
34
- )}
35
- <Button onClick={fetchData} loading={loading}>
36
- Refresh
37
- </Button>
38
- </Space>
39
- </Card>
40
- )
41
- }
@@ -1,8 +0,0 @@
1
- import { api, type ApiResponse } from '@robsun/keystone-web-core'
2
-
3
- export const getHello = async () => {
4
- const { data } = await api.get<ApiResponse<{ message: string; module: string }>>(
5
- '/example/hello'
6
- )
7
- return data.data
8
- }