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.
Files changed (79) hide show
  1. package/README.md +24 -23
  2. package/bin/create-freakpunch.mjs +6 -6
  3. package/files/README.md +19 -15
  4. package/files/app.config.ts +50 -58
  5. package/files/locales.ts +10 -0
  6. package/files/registry.ts +17 -0
  7. package/package.json +23 -23
  8. package/template/BACKEND.md +108 -86
  9. package/template/CLAUDE.md +15 -10
  10. package/template/design/COMPONENTS.md +1 -1
  11. package/template/design/DESIGN.md +3 -3
  12. package/template/design/PATTERNS.md +5 -5
  13. package/template/index.html +1 -1
  14. package/template/src/app.config.ts +50 -61
  15. package/template/src/app.roles.ts +24 -24
  16. package/template/src/core/api/demo.ts +8 -0
  17. package/template/src/core/audit/audit.service.ts +3 -34
  18. package/template/src/core/auth/auth.service.ts +25 -14
  19. package/template/src/core/auth/local-credentials.ts +46 -0
  20. package/template/src/core/auth/permissions.ts +1 -1
  21. package/template/src/core/company/company.service.ts +14 -12
  22. package/template/src/core/company/use-app-name.ts +7 -0
  23. package/template/src/core/components/page.tsx +4 -3
  24. package/template/src/core/config/runtime.ts +16 -14
  25. package/template/src/core/config/types.ts +3 -2
  26. package/template/src/core/layout/app-sidebar.tsx +19 -2
  27. package/template/src/core/notifications/notifications.service.ts +10 -115
  28. package/template/src/core/notifications/types.ts +5 -0
  29. package/template/src/core/setup/setup-gate.tsx +20 -0
  30. package/template/src/core/setup/setup.service.ts +101 -0
  31. package/template/src/core/users/users.service.ts +2 -2
  32. package/template/src/locales/en/auth.ts +2 -1
  33. package/template/src/locales/en/common.ts +1 -0
  34. package/template/src/locales/en/company.ts +3 -1
  35. package/template/src/locales/en/dashboard.ts +5 -0
  36. package/template/src/locales/en/setup.ts +45 -0
  37. package/template/src/locales/index.ts +4 -4
  38. package/template/src/locales/tr/auth.ts +2 -1
  39. package/template/src/locales/tr/common.ts +1 -0
  40. package/template/src/locales/tr/company.ts +3 -1
  41. package/template/src/locales/tr/dashboard.ts +5 -0
  42. package/template/src/locales/tr/setup.ts +49 -0
  43. package/template/src/modules/locales.ts +0 -4
  44. package/template/src/modules/registry.ts +17 -19
  45. package/template/src/pages/dashboard/dashboard-page.tsx +28 -243
  46. package/template/src/pages/dashboard/quick-actions.tsx +37 -0
  47. package/template/src/pages/login-page.tsx +26 -14
  48. package/template/src/pages/not-found-page.tsx +1 -2
  49. package/template/src/pages/settings/account/account.service.ts +22 -41
  50. package/template/src/pages/settings/account/notifications-page.tsx +3 -3
  51. package/template/src/pages/settings/company/general-page.tsx +6 -4
  52. package/template/src/pages/settings/company/integrations/integrations.service.ts +8 -28
  53. package/template/src/pages/settings/company/modules-page.tsx +10 -2
  54. package/template/src/pages/settings/company/roles-page.tsx +2 -1
  55. package/template/src/pages/setup-page.tsx +387 -0
  56. package/template/src/router.tsx +9 -4
  57. package/template/tools/new-module.mjs +90 -90
  58. package/template/tools/templates/module/index.ts.tpl +44 -37
  59. package/template/tools/templates/module/locales/en.ts.tpl +51 -45
  60. package/template/tools/templates/module/locales/tr.ts.tpl +55 -49
  61. package/template/{src/modules/products/pages/product-detail-page.tsx → tools/templates/module/pages/__id__-detail-page.tsx.tpl} +103 -108
  62. package/template/tools/templates/module/pages/__id__-form-page.tsx.tpl +121 -121
  63. package/template/tools/templates/module/pages/__id__-list-page.tsx.tpl +234 -237
  64. package/template/vite.config.ts +6 -3
  65. package/template/src/core/users/users.mock.ts +0 -24
  66. package/template/src/locales/en/landing.ts +0 -15
  67. package/template/src/locales/tr/landing.ts +0 -19
  68. package/template/src/modules/products/api/products.mock.ts +0 -46
  69. package/template/src/modules/products/api/products.service.ts +0 -84
  70. package/template/src/modules/products/components/product-status.tsx +0 -21
  71. package/template/src/modules/products/hooks.ts +0 -73
  72. package/template/src/modules/products/index.ts +0 -50
  73. package/template/src/modules/products/locales/en.ts +0 -78
  74. package/template/src/modules/products/locales/tr.ts +0 -82
  75. package/template/src/modules/products/pages/product-form-page.tsx +0 -211
  76. package/template/src/modules/products/pages/products-list-page.tsx +0 -286
  77. package/template/src/modules/products/types.ts +0 -61
  78. package/template/src/pages/dashboard/dashboard.service.ts +0 -92
  79. 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('/app/__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
- }
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
+ }