create-freakpunch 0.1.0 → 0.2.0
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 +24 -23
- package/bin/create-freakpunch.mjs +6 -6
- package/files/README.md +19 -15
- package/files/app.config.ts +50 -58
- package/files/locales.ts +10 -0
- package/files/registry.ts +17 -0
- package/package.json +23 -23
- package/template/BACKEND.md +108 -86
- package/template/CLAUDE.md +15 -10
- package/template/design/COMPONENTS.md +1 -1
- package/template/design/DESIGN.md +3 -3
- package/template/design/PATTERNS.md +5 -5
- package/template/index.html +1 -1
- package/template/src/app.config.ts +50 -61
- package/template/src/app.roles.ts +24 -24
- package/template/src/core/api/demo.ts +8 -0
- package/template/src/core/audit/audit.service.ts +3 -34
- package/template/src/core/auth/auth.service.ts +25 -14
- package/template/src/core/auth/local-credentials.ts +46 -0
- package/template/src/core/auth/permissions.ts +1 -1
- package/template/src/core/company/company.service.ts +14 -12
- package/template/src/core/company/use-app-name.ts +7 -0
- package/template/src/core/components/page.tsx +4 -3
- package/template/src/core/config/runtime.ts +16 -14
- package/template/src/core/config/types.ts +3 -2
- package/template/src/core/layout/app-sidebar.tsx +19 -2
- package/template/src/core/notifications/notifications.service.ts +10 -115
- package/template/src/core/notifications/types.ts +5 -0
- package/template/src/core/setup/setup-gate.tsx +20 -0
- package/template/src/core/setup/setup.service.ts +101 -0
- package/template/src/core/users/users.service.ts +2 -2
- package/template/src/locales/en/auth.ts +2 -1
- package/template/src/locales/en/common.ts +1 -0
- package/template/src/locales/en/company.ts +3 -1
- package/template/src/locales/en/dashboard.ts +5 -0
- package/template/src/locales/en/setup.ts +45 -0
- package/template/src/locales/index.ts +4 -4
- package/template/src/locales/tr/auth.ts +2 -1
- package/template/src/locales/tr/common.ts +1 -0
- package/template/src/locales/tr/company.ts +3 -1
- package/template/src/locales/tr/dashboard.ts +5 -0
- package/template/src/locales/tr/setup.ts +49 -0
- package/template/src/modules/locales.ts +0 -4
- package/template/src/modules/registry.ts +17 -19
- package/template/src/pages/dashboard/dashboard-page.tsx +28 -243
- package/template/src/pages/dashboard/quick-actions.tsx +37 -0
- package/template/src/pages/login-page.tsx +26 -14
- package/template/src/pages/not-found-page.tsx +1 -2
- package/template/src/pages/settings/account/account.service.ts +22 -41
- package/template/src/pages/settings/account/notifications-page.tsx +3 -3
- package/template/src/pages/settings/company/general-page.tsx +6 -4
- package/template/src/pages/settings/company/integrations/integrations.service.ts +8 -28
- package/template/src/pages/settings/company/modules-page.tsx +10 -2
- package/template/src/pages/settings/company/roles-page.tsx +2 -1
- package/template/src/pages/setup-page.tsx +387 -0
- package/template/src/router.tsx +9 -4
- package/template/tools/new-module.mjs +90 -90
- package/template/tools/templates/module/index.ts.tpl +44 -37
- package/template/tools/templates/module/locales/en.ts.tpl +51 -45
- package/template/tools/templates/module/locales/tr.ts.tpl +55 -49
- package/template/{src/modules/products/pages/product-detail-page.tsx → tools/templates/module/pages/__id__-detail-page.tsx.tpl} +103 -108
- package/template/tools/templates/module/pages/__id__-form-page.tsx.tpl +121 -121
- package/template/tools/templates/module/pages/__id__-list-page.tsx.tpl +234 -237
- package/template/vite.config.ts +6 -3
- package/template/src/core/users/users.mock.ts +0 -24
- package/template/src/locales/en/landing.ts +0 -15
- package/template/src/locales/tr/landing.ts +0 -19
- package/template/src/modules/products/api/products.mock.ts +0 -46
- package/template/src/modules/products/api/products.service.ts +0 -84
- package/template/src/modules/products/components/product-status.tsx +0 -21
- package/template/src/modules/products/hooks.ts +0 -73
- package/template/src/modules/products/index.ts +0 -50
- package/template/src/modules/products/locales/en.ts +0 -78
- package/template/src/modules/products/locales/tr.ts +0 -82
- package/template/src/modules/products/pages/product-form-page.tsx +0 -211
- package/template/src/modules/products/pages/products-list-page.tsx +0 -286
- package/template/src/modules/products/types.ts +0 -61
- package/template/src/pages/dashboard/dashboard.service.ts +0 -92
- package/template/src/pages/landing-page.tsx +0 -79
|
@@ -1,121 +1,121 @@
|
|
|
1
|
-
import { Controller, useForm } from 'react-hook-form'
|
|
2
|
-
import { zodResolver } from '@hookform/resolvers/zod'
|
|
3
|
-
import { useTranslation } from 'react-i18next'
|
|
4
|
-
import { useNavigate, useParams } from 'react-router'
|
|
5
|
-
import { toast } from 'sonner'
|
|
6
|
-
|
|
7
|
-
import { Field, FieldDescription, FieldLabel } from '@/components/ui/field'
|
|
8
|
-
import { Input } from '@/components/ui/input'
|
|
9
|
-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
|
10
|
-
import { Textarea } from '@/components/ui/textarea'
|
|
11
|
-
import { ButtonLink } from '@/core/components/button-link'
|
|
12
|
-
import { FormFieldError } from '@/core/components/form-error'
|
|
13
|
-
import { FormLayout, FormSection } from '@/core/components/form-page'
|
|
14
|
-
import { Page } from '@/core/components/page'
|
|
15
|
-
import { ErrorState, LoadingState } from '@/core/components/states'
|
|
16
|
-
import { useCreate__Name__, useUpdate__Name__, use__Name__Record } from '../hooks'
|
|
17
|
-
import { __id__FormSchema, __id__Statuses, type __Name__Input, type __Name__Record } from '../types'
|
|
18
|
-
|
|
19
|
-
const empty: __Name__Input = { name: '', status: 'draft', notes: '' }
|
|
20
|
-
|
|
21
|
-
/** /app/__id__/new and /app/__id__/:id/edit */
|
|
22
|
-
export default function __Name__FormPage() {
|
|
23
|
-
const { id } = useParams()
|
|
24
|
-
const query = use__Name__Record(id)
|
|
25
|
-
const { t } = useTranslation('__id__')
|
|
26
|
-
|
|
27
|
-
if (!id) return <__Name__Form />
|
|
28
|
-
if (query.isPending) return <LoadingState />
|
|
29
|
-
if (query.error) {
|
|
30
|
-
return (
|
|
31
|
-
<ErrorState
|
|
32
|
-
error={query.error}
|
|
33
|
-
onRetry={() => query.refetch()}
|
|
34
|
-
notFoundAction={
|
|
35
|
-
<ButtonLink to="/app/__id__" variant="outline">
|
|
36
|
-
{t('form.back')}
|
|
37
|
-
</ButtonLink>
|
|
38
|
-
}
|
|
39
|
-
/>
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
return <__Name__Form key={query.data.id} record={query.data} />
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function __Name__Form({ record }: { record?: __Name__Record }) {
|
|
46
|
-
const navigate = useNavigate()
|
|
47
|
-
const create = useCreate__Name__()
|
|
48
|
-
const update = useUpdate__Name__(record?.id ?? '')
|
|
49
|
-
const { t } = useTranslation(['__id__', 'common'])
|
|
50
|
-
const isEdit = Boolean(record)
|
|
51
|
-
|
|
52
|
-
const form = useForm<__Name__Input>({
|
|
53
|
-
resolver: zodResolver(__id__FormSchema),
|
|
54
|
-
defaultValues: record ? { name: record.name, status: record.status, notes: record.notes } : empty,
|
|
55
|
-
})
|
|
56
|
-
const { register, control, handleSubmit, formState } = form
|
|
57
|
-
const { errors } = formState
|
|
58
|
-
|
|
59
|
-
const onSubmit = handleSubmit(async (values) => {
|
|
60
|
-
try {
|
|
61
|
-
const saved = isEdit ? await update.mutateAsync(values) : await create.mutateAsync(values)
|
|
62
|
-
toast.success(isEdit ? t('form.saved') : t('form.created'), { description: saved.name })
|
|
63
|
-
navigate(
|
|
64
|
-
} catch (e) {
|
|
65
|
-
toast.error(t('form.saveFailed'), { description: e instanceof Error ? e.message : undefined })
|
|
66
|
-
}
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
const statusItems = __id__Statuses.map((s) => ({ value: s, label: t(`status.${s}`) }))
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<Page
|
|
73
|
-
title={isEdit ? t('form.editTitle', { name: record!.name }) : t('form.newTitle')}
|
|
74
|
-
description={isEdit ? t('form.editDescription') : t('form.newDescription')}
|
|
75
|
-
>
|
|
76
|
-
<FormLayout
|
|
77
|
-
onSubmit={onSubmit}
|
|
78
|
-
submitLabel={isEdit ? t('common:actions.saveChanges') : t('form.create')}
|
|
79
|
-
isSubmitting={formState.isSubmitting}
|
|
80
|
-
isDirty={!isEdit || formState.isDirty}
|
|
81
|
-
onCancel={() => navigate(-1)}
|
|
82
|
-
>
|
|
83
|
-
<FormSection title={t('form.sections.details')} description={t('form.sections.detailsDescription')}>
|
|
84
|
-
<Field data-invalid={!!errors.name}>
|
|
85
|
-
<FieldLabel htmlFor="name">{t('form.fields.name')}</FieldLabel>
|
|
86
|
-
<Input id="name" autoFocus={!isEdit} aria-invalid={!!errors.name} {...register('name')} />
|
|
87
|
-
<FormFieldError error={errors.name} />
|
|
88
|
-
</Field>
|
|
89
|
-
<Field data-invalid={!!errors.status}>
|
|
90
|
-
<FieldLabel htmlFor="status">{t('form.fields.status')}</FieldLabel>
|
|
91
|
-
<Controller
|
|
92
|
-
control={control}
|
|
93
|
-
name="status"
|
|
94
|
-
render={({ field }) => (
|
|
95
|
-
<Select items={statusItems} value={field.value} onValueChange={(v) => v && field.onChange(v)}>
|
|
96
|
-
<SelectTrigger id="status" className="w-full" onBlur={field.onBlur}>
|
|
97
|
-
<SelectValue />
|
|
98
|
-
</SelectTrigger>
|
|
99
|
-
<SelectContent>
|
|
100
|
-
{statusItems.map((s) => (
|
|
101
|
-
<SelectItem key={s.value} value={s.value}>
|
|
102
|
-
{s.label}
|
|
103
|
-
</SelectItem>
|
|
104
|
-
))}
|
|
105
|
-
</SelectContent>
|
|
106
|
-
</Select>
|
|
107
|
-
)}
|
|
108
|
-
/>
|
|
109
|
-
<FormFieldError error={errors.status} />
|
|
110
|
-
</Field>
|
|
111
|
-
<Field data-invalid={!!errors.notes}>
|
|
112
|
-
<FieldLabel htmlFor="notes">{t('form.fields.notes')}</FieldLabel>
|
|
113
|
-
<Textarea id="notes" rows={4} aria-invalid={!!errors.notes} {...register('notes')} />
|
|
114
|
-
<FieldDescription>{t('form.fields.notesHint')}</FieldDescription>
|
|
115
|
-
<FormFieldError error={errors.notes} />
|
|
116
|
-
</Field>
|
|
117
|
-
</FormSection>
|
|
118
|
-
</FormLayout>
|
|
119
|
-
</Page>
|
|
120
|
-
)
|
|
121
|
-
}
|
|
1
|
+
import { Controller, useForm } from 'react-hook-form'
|
|
2
|
+
import { zodResolver } from '@hookform/resolvers/zod'
|
|
3
|
+
import { useTranslation } from 'react-i18next'
|
|
4
|
+
import { useNavigate, useParams } from 'react-router'
|
|
5
|
+
import { toast } from 'sonner'
|
|
6
|
+
|
|
7
|
+
import { Field, FieldDescription, FieldLabel } from '@/components/ui/field'
|
|
8
|
+
import { Input } from '@/components/ui/input'
|
|
9
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
|
10
|
+
import { Textarea } from '@/components/ui/textarea'
|
|
11
|
+
import { ButtonLink } from '@/core/components/button-link'
|
|
12
|
+
import { FormFieldError } from '@/core/components/form-error'
|
|
13
|
+
import { FormLayout, FormSection } from '@/core/components/form-page'
|
|
14
|
+
import { Page } from '@/core/components/page'
|
|
15
|
+
import { ErrorState, LoadingState } from '@/core/components/states'
|
|
16
|
+
import { useCreate__Name__, useUpdate__Name__, use__Name__Record } from '../hooks'
|
|
17
|
+
import { __id__FormSchema, __id__Statuses, type __Name__Input, type __Name__Record } from '../types'
|
|
18
|
+
|
|
19
|
+
const empty: __Name__Input = { name: '', status: 'draft', notes: '' }
|
|
20
|
+
|
|
21
|
+
/** /app/__id__/new and /app/__id__/:id/edit */
|
|
22
|
+
export default function __Name__FormPage() {
|
|
23
|
+
const { id } = useParams()
|
|
24
|
+
const query = use__Name__Record(id)
|
|
25
|
+
const { t } = useTranslation('__id__')
|
|
26
|
+
|
|
27
|
+
if (!id) return <__Name__Form />
|
|
28
|
+
if (query.isPending) return <LoadingState />
|
|
29
|
+
if (query.error) {
|
|
30
|
+
return (
|
|
31
|
+
<ErrorState
|
|
32
|
+
error={query.error}
|
|
33
|
+
onRetry={() => query.refetch()}
|
|
34
|
+
notFoundAction={
|
|
35
|
+
<ButtonLink to="/app/__id__" variant="outline">
|
|
36
|
+
{t('form.back')}
|
|
37
|
+
</ButtonLink>
|
|
38
|
+
}
|
|
39
|
+
/>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
return <__Name__Form key={query.data.id} record={query.data} />
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function __Name__Form({ record }: { record?: __Name__Record }) {
|
|
46
|
+
const navigate = useNavigate()
|
|
47
|
+
const create = useCreate__Name__()
|
|
48
|
+
const update = useUpdate__Name__(record?.id ?? '')
|
|
49
|
+
const { t } = useTranslation(['__id__', 'common'])
|
|
50
|
+
const isEdit = Boolean(record)
|
|
51
|
+
|
|
52
|
+
const form = useForm<__Name__Input>({
|
|
53
|
+
resolver: zodResolver(__id__FormSchema),
|
|
54
|
+
defaultValues: record ? { name: record.name, status: record.status, notes: record.notes } : empty,
|
|
55
|
+
})
|
|
56
|
+
const { register, control, handleSubmit, formState } = form
|
|
57
|
+
const { errors } = formState
|
|
58
|
+
|
|
59
|
+
const onSubmit = handleSubmit(async (values) => {
|
|
60
|
+
try {
|
|
61
|
+
const saved = isEdit ? await update.mutateAsync(values) : await create.mutateAsync(values)
|
|
62
|
+
toast.success(isEdit ? t('form.saved') : t('form.created'), { description: saved.name })
|
|
63
|
+
navigate(`/app/__id__/${saved.id}`)
|
|
64
|
+
} catch (e) {
|
|
65
|
+
toast.error(t('form.saveFailed'), { description: e instanceof Error ? e.message : undefined })
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const statusItems = __id__Statuses.map((s) => ({ value: s, label: t(`status.${s}`) }))
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<Page
|
|
73
|
+
title={isEdit ? t('form.editTitle', { name: record!.name }) : t('form.newTitle')}
|
|
74
|
+
description={isEdit ? t('form.editDescription') : t('form.newDescription')}
|
|
75
|
+
>
|
|
76
|
+
<FormLayout
|
|
77
|
+
onSubmit={onSubmit}
|
|
78
|
+
submitLabel={isEdit ? t('common:actions.saveChanges') : t('form.create')}
|
|
79
|
+
isSubmitting={formState.isSubmitting}
|
|
80
|
+
isDirty={!isEdit || formState.isDirty}
|
|
81
|
+
onCancel={() => navigate(-1)}
|
|
82
|
+
>
|
|
83
|
+
<FormSection title={t('form.sections.details')} description={t('form.sections.detailsDescription')}>
|
|
84
|
+
<Field data-invalid={!!errors.name}>
|
|
85
|
+
<FieldLabel htmlFor="name">{t('form.fields.name')}</FieldLabel>
|
|
86
|
+
<Input id="name" autoFocus={!isEdit} aria-invalid={!!errors.name} {...register('name')} />
|
|
87
|
+
<FormFieldError error={errors.name} />
|
|
88
|
+
</Field>
|
|
89
|
+
<Field data-invalid={!!errors.status}>
|
|
90
|
+
<FieldLabel htmlFor="status">{t('form.fields.status')}</FieldLabel>
|
|
91
|
+
<Controller
|
|
92
|
+
control={control}
|
|
93
|
+
name="status"
|
|
94
|
+
render={({ field }) => (
|
|
95
|
+
<Select items={statusItems} value={field.value} onValueChange={(v) => v && field.onChange(v)}>
|
|
96
|
+
<SelectTrigger id="status" className="w-full" onBlur={field.onBlur}>
|
|
97
|
+
<SelectValue />
|
|
98
|
+
</SelectTrigger>
|
|
99
|
+
<SelectContent>
|
|
100
|
+
{statusItems.map((s) => (
|
|
101
|
+
<SelectItem key={s.value} value={s.value}>
|
|
102
|
+
{s.label}
|
|
103
|
+
</SelectItem>
|
|
104
|
+
))}
|
|
105
|
+
</SelectContent>
|
|
106
|
+
</Select>
|
|
107
|
+
)}
|
|
108
|
+
/>
|
|
109
|
+
<FormFieldError error={errors.status} />
|
|
110
|
+
</Field>
|
|
111
|
+
<Field data-invalid={!!errors.notes}>
|
|
112
|
+
<FieldLabel htmlFor="notes">{t('form.fields.notes')}</FieldLabel>
|
|
113
|
+
<Textarea id="notes" rows={4} aria-invalid={!!errors.notes} {...register('notes')} />
|
|
114
|
+
<FieldDescription>{t('form.fields.notesHint')}</FieldDescription>
|
|
115
|
+
<FormFieldError error={errors.notes} />
|
|
116
|
+
</Field>
|
|
117
|
+
</FormSection>
|
|
118
|
+
</FormLayout>
|
|
119
|
+
</Page>
|
|
120
|
+
)
|
|
121
|
+
}
|