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,5 +1,5 @@
1
- import { appConfig } from '@/app.config'
2
1
  import { ApiError } from '@/core/api/http'
2
+ import { demoSeed } from '@/core/api/demo'
3
3
  import { createMockDocument, delay } from '@/core/api/mock'
4
4
  import { authStore } from '@/core/auth/auth-store'
5
5
  import type { Role } from '@/core/auth/roles'
@@ -39,118 +39,15 @@ type NotificationsDoc = {
39
39
  lastArrivalAt: number
40
40
  }
41
41
 
42
- type Draft = Omit<AppNotification, 'id' | 'createdAt' | 'readAt'>
42
+ export type Draft = Omit<AppNotification, 'id' | 'createdAt' | 'readAt'>
43
43
 
44
44
  const minute = 60_000
45
- const hasProducts = appConfig.modules.products
46
- const links = {
47
- users: '/app/settings/company/users',
48
- general: '/app/settings/company/general',
49
- branding: '/app/settings/company/branding',
50
- security: '/app/settings/security',
51
- lowStock: '/app/products?status=active&sort=stock.asc',
52
- recentProducts: '/app/products?sort=updatedAt.desc',
53
- dashboard: '/app',
54
- }
55
-
56
- // Building blocks for the seed and for the notifications that arrive later.
57
- const drafts = {
58
- userInvited: (email: string, role: Role, name: string): Draft => ({
59
- event: 'userJoined', type: 'info', template: 'userInvited', params: { email, role, name }, link: links.users,
60
- }),
61
- userJoined: (name: string): Draft => ({
62
- event: 'userJoined', type: 'success', template: 'userJoined', params: { name }, link: links.users,
63
- }),
64
- roleChangedOther: (name: string, role: Role): Draft => ({
65
- event: 'roleChanged', type: 'info', template: 'roleChangedOther', params: { name, role }, link: links.users,
66
- }),
67
- roleChangedYou: (role: Role, name: string): Draft => ({
68
- event: 'roleChanged', type: 'info', template: 'roleChangedYou', params: { role, name }, link: '/app/settings/profile',
69
- }),
70
- lowStock: (count: number): Draft => ({
71
- event: 'lowStock', type: 'warning', template: 'lowStock', params: { count }, link: links.lowStock,
72
- }),
73
- productUpdated: (name: string, count: number): Draft => ({
74
- event: 'productChanged', type: 'info', template: 'productUpdated', params: { name, count }, link: links.recentProducts,
75
- }),
76
- settingsChanged: (name: string): Draft => ({
77
- event: 'settingsChanged', type: 'info', template: 'settingsChanged', params: { name }, link: links.general,
78
- }),
79
- brandingChanged: (name: string): Draft => ({
80
- event: 'settingsChanged', type: 'success', template: 'brandingChanged', params: { name }, link: links.branding,
81
- }),
82
- securityAlert: (browser: string, os: string, location: string): Draft => ({
83
- event: 'newSignIn', type: 'error', template: 'securityAlert', params: { browser, os, location }, link: links.security,
84
- }),
85
- passwordChanged: (): Draft => ({
86
- event: 'passwordChanged', type: 'success', template: 'passwordChanged', params: {}, link: links.security,
87
- }),
88
- weeklySummary: (orders: number, revenue: number): Draft => ({
89
- event: 'weeklySummary', type: 'success', template: 'weeklySummary', params: { orders, revenue }, link: links.dashboard,
90
- }),
91
- announcement: (): Draft => ({ event: 'announcements', type: 'info', template: 'announcement', params: {} }),
92
- }
93
-
94
- const isProductEvent = (d: Draft) => d.event === 'lowStock' || d.event === 'productChanged'
95
-
96
- /** What each role starts with: [draft, minutes ago, read]. */
97
- function seedFor(role: Role): [Draft, number, boolean][] {
98
- const peer = role === 'owner' ? 'Maya Chen' : 'Deniz Aydın'
99
- switch (role) {
100
- case 'owner':
101
- case 'admin':
102
- return [
103
- [drafts.userInvited('lena.fischer@freakpunch.site', 'editor', peer), 18, false],
104
- [drafts.lowStock(3), 95, false],
105
- [drafts.securityAlert('Edge', 'Windows', 'Berlin, DE'), 260, false],
106
- [drafts.roleChangedOther('Can Yılmaz', 'admin'), 60 * 26, true],
107
- [drafts.settingsChanged(peer), 60 * 49, true],
108
- [drafts.userJoined('Burak Öztürk'), 60 * 75, true],
109
- [drafts.brandingChanged(peer), 60 * 24 * 6, true],
110
- ]
111
- case 'editor':
112
- return [
113
- [drafts.lowStock(3), 40, false],
114
- [drafts.productUpdated('Elif Şahin', 4), 130, false],
115
- [drafts.securityAlert('Safari', 'iOS', 'Ankara, TR'), 60 * 20, true],
116
- [drafts.roleChangedYou('editor', 'Maya Chen'), 60 * 24 * 3, true],
117
- [drafts.weeklySummary(128, 18_420), 60 * 24 * 5, true],
118
- ]
119
- case 'viewer':
120
- default:
121
- return [
122
- [drafts.weeklySummary(128, 18_420), 55, false],
123
- [drafts.roleChangedYou('viewer', 'Maya Chen'), 60 * 7, false],
124
- [drafts.lowStock(2), 60 * 30, true],
125
- [drafts.passwordChanged(), 60 * 24 * 4, true],
126
- [drafts.announcement(), 60 * 24 * 8, true],
127
- ]
128
- }
129
- }
130
-
131
- /** Pool the mock picks from when a "new" notification arrives. */
132
- function arrivalsFor(role: Role): (() => Draft)[] {
133
- const pick = <T,>(items: readonly T[]) => items[Math.floor(Math.random() * items.length)]!
134
- const names = ['Elif Şahin', 'Can Yılmaz', 'Maya Chen', 'Jonas Weber']
135
- const emails = ['mert.kaya', 'zeynep.arslan', 'noah.becker', 'ayse.demir'].map((n) => `${n}@freakpunch.site`)
136
- const lowStock = () => drafts.lowStock(1 + Math.floor(Math.random() * 4))
137
- switch (role) {
138
- case 'owner':
139
- case 'admin':
140
- return [
141
- () => drafts.userInvited(pick(emails), pick(['editor', 'viewer'] as const), pick(names)),
142
- () => drafts.settingsChanged(pick(names)),
143
- () => drafts.securityAlert('Chrome', 'Linux', pick(['Amsterdam, NL', 'İzmir, TR'])),
144
- lowStock,
145
- ]
146
- case 'editor':
147
- return [() => drafts.productUpdated(pick(names), 1 + Math.floor(Math.random() * 5)), lowStock]
148
- case 'viewer':
149
- default:
150
- return [() => drafts.weeklySummary(90 + Math.floor(Math.random() * 80), 12_000 + Math.floor(Math.random() * 9_000)), lowStock]
151
- }
152
- }
153
45
 
46
+ /** Sample feed; without it the list starts empty and only a real backend adds to it. */
47
+ const demo = demoSeed<{
48
+ seedFor(role: Role): [Draft, number, boolean][]
49
+ arrivalsFor(role: Role): (() => Draft)[]
50
+ }>('notifications')
154
51
  function createMockNotificationsService(): NotificationsService {
155
52
  const docs = new Map<string, ReturnType<typeof createMockDocument<NotificationsDoc>>>()
156
53
 
@@ -162,9 +59,7 @@ function createMockNotificationsService(): NotificationsService {
162
59
  user.id,
163
60
  createMockDocument(`notifications:${user.id}`, () => {
164
61
  const now = Date.now()
165
- const items = seedFor(user.role)
166
- .filter(([d]) => hasProducts || !isProductEvent(d))
167
- .map(([draft, ago, read], i): AppNotification => {
62
+ const items = (demo?.seedFor(user.role) ?? []).map(([draft, ago, read], i): AppNotification => {
168
63
  const createdAt = new Date(now - ago * minute).toISOString()
169
64
  return { ...draft, id: `n_${user.id}_${i + 1}`, createdAt, readAt: read ? createdAt : undefined }
170
65
  })
@@ -189,9 +84,9 @@ function createMockNotificationsService(): NotificationsService {
189
84
  const data = doc.get()
190
85
  if (Date.now() - data.lastArrivalAt < 45_000 || Math.random() > 0.4) return
191
86
  const settings = settingsOf(data)
192
- const pool = arrivalsFor(role)
87
+ const pool = (demo?.arrivalsFor(role) ?? [])
193
88
  .map((make) => make())
194
- .filter((d) => settings[d.event].inApp && (hasProducts || !isProductEvent(d)))
89
+ .filter((d) => settings[d.event].inApp)
195
90
  const draft = pool[Math.floor(Math.random() * pool.length)]
196
91
  const now = Date.now()
197
92
  if (!draft) return save({ lastArrivalAt: now })
@@ -1,3 +1,5 @@
1
+ import { isDemo } from '@/core/config/runtime'
2
+
1
3
  /**
2
4
  * Notification event types. One catalog drives both the Settings → Notifications
3
5
  * toggles (email / in-app per event) and the in-app notification feed: a
@@ -15,6 +17,9 @@ export const notificationEvents = [
15
17
  { id: 'announcements', group: 'updates' },
16
18
  ] as const
17
19
 
20
+ /** What Settings → Notifications lists; catalog events belong to the demo's sample module. */
21
+ export const visibleNotificationEvents = notificationEvents.filter((e) => isDemo || e.group !== 'catalog')
22
+
18
23
  export type NotificationEventId = (typeof notificationEvents)[number]['id']
19
24
  export type NotificationGroup = (typeof notificationEvents)[number]['group']
20
25
  export type NotificationChannels = { email: boolean; inApp: boolean }
@@ -0,0 +1,20 @@
1
+ import { useQuery } from '@tanstack/react-query'
2
+ import { Navigate, Outlet, useLocation } from 'react-router'
3
+
4
+ import { setupService } from './setup.service'
5
+
6
+ /** Sends a brand-new install to the setup wizard, and keeps finished installs out of it. */
7
+ export function SetupGate() {
8
+ const { pathname } = useLocation()
9
+ const { data: needsSetup, isPending } = useQuery({
10
+ queryKey: ['setup', 'status'],
11
+ queryFn: () => setupService.needsSetup(),
12
+ staleTime: Infinity,
13
+ retry: false,
14
+ })
15
+
16
+ if (isPending) return <div className="min-h-svh bg-canvas" />
17
+ if (needsSetup && pathname !== '/setup') return <Navigate to="/setup" replace />
18
+ if (!needsSetup && pathname === '/setup') return <Navigate to="/app" replace />
19
+ return <Outlet />
20
+ }
@@ -0,0 +1,101 @@
1
+ import { ApiError, http } from '@/core/api/http'
2
+ import { delay } from '@/core/api/mock'
3
+ import { authStore } from '@/core/auth/auth-store'
4
+ import { fetchCsrfCookie, startLocalSession, type AuthResponse } from '@/core/auth/auth.service'
5
+ import { localCredentials } from '@/core/auth/local-credentials'
6
+ import { createCompanySettings } from '@/core/company/company.service'
7
+ import type { CompanyGeneral, Currency } from '@/core/company/types'
8
+ import { authMode, isDemo } from '@/core/config/runtime'
9
+ import { storage } from '@/core/lib/storage'
10
+ import type { Language } from '@/core/i18n/types'
11
+ import type { BrandSetting } from '@/core/theme/brand'
12
+ import type { User } from '@/core/users/types'
13
+
14
+ export type SetupInput = {
15
+ company: { name: string; language: Language; timeZone: string; currency: Currency }
16
+ admin: { firstName: string; lastName: string; email: string; password: string }
17
+ branding: { brand: BrandSetting; logo?: string }
18
+ }
19
+
20
+ /**
21
+ * First run: create the company and its Owner. With a backend these are
22
+ * GET /setup/status and POST /setup (see BACKEND.md), and the server must
23
+ * refuse POST /setup once an Owner exists.
24
+ */
25
+ export interface SetupService {
26
+ needsSetup(): Promise<boolean>
27
+ complete(input: SetupInput): Promise<void>
28
+ }
29
+
30
+ /** Company name, logo and brand stay in the company service until it is connected to a backend too. */
31
+ function saveCompany({ company, admin, branding }: SetupInput) {
32
+ const general: Partial<CompanyGeneral> = {
33
+ name: company.name,
34
+ contactEmail: admin.email,
35
+ defaultLanguage: company.language,
36
+ timeZone: company.timeZone,
37
+ currency: company.currency,
38
+ }
39
+ storage.set('mock:company', createCompanySettings(general, { brand: branding.brand, logoLight: branding.logo, logoDark: branding.logo }))
40
+ }
41
+
42
+ /** Clears anything left in this browser, such as data from another project on the same address. */
43
+ function clearLocalData() {
44
+ for (const key of Object.keys(localStorage)) {
45
+ if (key.startsWith('fp:mock:')) storage.remove(key.slice(3))
46
+ }
47
+ authStore.clear()
48
+ }
49
+
50
+ const localSetupService: SetupService = {
51
+ async needsSetup() {
52
+ return !localCredentials.any()
53
+ },
54
+
55
+ async complete(input) {
56
+ await delay(300, 500)
57
+ if (localCredentials.any()) throw new ApiError(409, 'already_set_up')
58
+ clearLocalData()
59
+ const { firstName, lastName, email, password } = input.admin
60
+ const now = new Date().toISOString()
61
+ const owner: User = {
62
+ id: 'u_owner',
63
+ firstName,
64
+ lastName,
65
+ email: email.trim().toLowerCase(),
66
+ role: 'owner',
67
+ status: 'active',
68
+ createdAt: now,
69
+ lastActiveAt: now,
70
+ }
71
+ storage.set('mock:users', [owner])
72
+ saveCompany(input)
73
+ await localCredentials.set(owner.id, password)
74
+ startLocalSession(owner)
75
+ },
76
+ }
77
+
78
+ const restSetupService: SetupService = {
79
+ async needsSetup() {
80
+ // A backend that creates its admin another way (seeder, CLI) simply doesn't have this endpoint.
81
+ try {
82
+ return (await http.get<{ needsSetup: boolean }>('/setup/status')).needsSetup
83
+ } catch {
84
+ return false
85
+ }
86
+ },
87
+
88
+ async complete(input) {
89
+ await fetchCsrfCookie()
90
+ const { user, token, expiresAt } = await http.post<AuthResponse>('/setup', input)
91
+ saveCompany(input)
92
+ authStore.set({ user, token, expiresAt: expiresAt ?? Date.now() + 7 * 86_400_000 })
93
+ },
94
+ }
95
+
96
+ const demoSetupService: SetupService = {
97
+ needsSetup: async () => false,
98
+ complete: async () => {},
99
+ }
100
+
101
+ export const setupService: SetupService = isDemo ? demoSetupService : authMode === 'mock' ? localSetupService : restSetupService
@@ -1,3 +1,4 @@
1
+ import { demoSeed } from '@/core/api/demo'
1
2
  import { ApiError } from '@/core/api/http'
2
3
  import { createMockCollection, delay, notFound, paginate } from '@/core/api/mock'
3
4
  import { auditService } from '@/core/audit/audit.service'
@@ -5,7 +6,6 @@ import type { ListParams, Paginated } from '@/core/api/types'
5
6
  import { authStore } from '@/core/auth/auth-store'
6
7
  import { canManageUser, type Role } from '@/core/auth/permissions'
7
8
  import { fullName, type AssignableRole, type InviteInput, type ProfilePatch, type User, type UserFilters } from './types'
8
- import { createUserSeed } from './users.mock'
9
9
 
10
10
  /**
11
11
  * Team members. Screens depend only on this interface; a real backend
@@ -37,7 +37,7 @@ export const userErrors = {
37
37
  } as const
38
38
 
39
39
  function createMockUsersService(): UsersService {
40
- const db = createMockCollection('users', createUserSeed)
40
+ const db = createMockCollection('users', () => demoSeed<() => User[]>('users')?.() ?? [])
41
41
  const find = (id: string) => db.find(id) ?? notFound('User', id)
42
42
  const nextId = () => `u_${String(db.all().length + 1).padStart(4, '0')}_${Date.now().toString(36)}`
43
43
  const actor = () => authStore.getSession()?.user ?? null
@@ -7,7 +7,8 @@ export default {
7
7
  email: 'Email',
8
8
  password: 'Password',
9
9
  submit: 'Continue',
10
- demo: 'Demo mode: any password works. Sign in as owner@, admin@, editor@ or viewer@freakpunch.site to try a role.',
10
+ demo: 'Demo mode: any password works. Sign in as owner@, admin@, editor@ or viewer@{{domain}} to try a role.',
11
+ invalid: 'The email or password is incorrect.',
11
12
  welcome: 'Welcome, {{name}}',
12
13
  failed: 'Sign in failed',
13
14
  passwordRequired: 'Enter your password',
@@ -64,6 +64,7 @@ export default {
64
64
  preferences: 'Preferences',
65
65
  },
66
66
  actions: {
67
+ back: 'Back',
67
68
  save: 'Save',
68
69
  saveChanges: 'Save changes',
69
70
  cancel: 'Cancel',
@@ -10,7 +10,7 @@ export default {
10
10
  description: 'How your company appears in the app and in emails.',
11
11
  name: 'Company name',
12
12
  legalName: 'Legal name',
13
- legalNameHint: 'As registered, e.g. “Acme Ticaret A.Ş.”',
13
+ legalNameHint: 'The full name as officially registered.',
14
14
  },
15
15
  tax: {
16
16
  title: 'Tax information',
@@ -203,6 +203,8 @@ export default {
203
203
  },
204
204
  },
205
205
  modules: {
206
+ emptyTitle: 'No modules yet',
207
+ emptyDescription: 'Features you add to this panel will be listed here.',
206
208
  title: 'Modules',
207
209
  description: 'Feature areas available in this workspace.',
208
210
  note: 'Read-only for now. Modules are switched on in <code>src/app.config.ts</code>; this page will be editable once the backend stores the setting.',
@@ -1,5 +1,10 @@
1
1
  /** Dashboard home. */
2
2
  export default {
3
+ readyDescription: 'Your panel is ready.',
4
+ noModules: {
5
+ title: 'Nothing here yet',
6
+ description: 'Features you add to this panel will appear here and in the sidebar.',
7
+ },
3
8
  greeting: {
4
9
  morning: 'Good morning, {{name}}',
5
10
  afternoon: 'Good afternoon, {{name}}',
@@ -0,0 +1,45 @@
1
+ /** First-run setup wizard. */
2
+ export default {
3
+ title: 'Set up your panel',
4
+ progress: 'Step {{step}} of {{total}}',
5
+ next: 'Continue',
6
+ finish: 'Finish setup',
7
+ failed: 'Setup failed',
8
+ insecure: 'Open the panel on https or localhost to create an account.',
9
+ localNote: 'No backend connected yet: this account and its data are saved only in this browser. Connect a backend before going live.',
10
+ company: {
11
+ title: 'Your company',
12
+ description: 'This name appears across the panel. You can change everything later in Settings.',
13
+ name: 'Company name',
14
+ language: 'Language',
15
+ timeZone: 'Time zone',
16
+ currency: 'Currency',
17
+ },
18
+ admin: {
19
+ title: 'Your admin account',
20
+ description: 'You will be the Owner, with full access to the panel.',
21
+ firstName: 'First name',
22
+ lastName: 'Last name',
23
+ email: 'Email',
24
+ password: 'Password',
25
+ confirmPassword: 'Confirm password',
26
+ passwordWeak: 'This password does not meet the rules below',
27
+ passwordMismatch: 'The passwords do not match',
28
+ },
29
+ branding: {
30
+ title: 'Your look',
31
+ description: 'Pick an accent color and add a logo. The logo is optional.',
32
+ color: 'Accent color',
33
+ logo: 'Logo',
34
+ },
35
+ review: {
36
+ title: 'Check the details',
37
+ description: 'Everything look right?',
38
+ admin: 'Owner',
39
+ },
40
+ done: {
41
+ title: 'You are all set',
42
+ description: '{{name}} is ready to use.',
43
+ open: 'Open the panel',
44
+ },
45
+ }
@@ -13,26 +13,26 @@ import companyEn from './en/company'
13
13
  import commonEn from './en/common'
14
14
  import dashboardEn from './en/dashboard'
15
15
  import errorsEn from './en/errors'
16
- import landingEn from './en/landing'
17
16
  import notificationsEn from './en/notifications'
18
17
  import settingsEn from './en/settings'
18
+ import setupEn from './en/setup'
19
19
  import accountTr from './tr/account'
20
20
  import authTr from './tr/auth'
21
21
  import companyTr from './tr/company'
22
22
  import commonTr from './tr/common'
23
23
  import dashboardTr from './tr/dashboard'
24
24
  import errorsTr from './tr/errors'
25
- import landingTr from './tr/landing'
26
25
  import notificationsTr from './tr/notifications'
27
26
  import settingsTr from './tr/settings'
27
+ import setupTr from './tr/setup'
28
28
 
29
29
  const en = {
30
30
  common: commonEn,
31
31
  auth: authEn,
32
- landing: landingEn,
33
32
  errors: errorsEn,
34
33
  dashboard: dashboardEn,
35
34
  settings: settingsEn,
35
+ setup: setupEn,
36
36
  account: accountEn,
37
37
  company: companyEn,
38
38
  notifications: notificationsEn,
@@ -42,10 +42,10 @@ const en = {
42
42
  const tr: typeof en = {
43
43
  common: commonTr,
44
44
  auth: authTr,
45
- landing: landingTr,
46
45
  errors: errorsTr,
47
46
  dashboard: dashboardTr,
48
47
  settings: settingsTr,
48
+ setup: setupTr,
49
49
  account: accountTr,
50
50
  company: companyTr,
51
51
  notifications: notificationsTr,
@@ -9,7 +9,8 @@ const auth: Messages<typeof en> = {
9
9
  email: 'E-posta',
10
10
  password: 'Şifre',
11
11
  submit: 'Devam et',
12
- demo: 'Demo modu: her şifre çalışır. Bir rolü denemek için owner@, admin@, editor@ veya viewer@freakpunch.site ile oturum açın.',
12
+ demo: 'Demo modu: her şifre çalışır. Bir rolü denemek için owner@, admin@, editor@ veya viewer@{{domain}} ile oturum açın.',
13
+ invalid: 'E-posta veya şifre hatalı.',
13
14
  welcome: 'Hoş geldiniz, {{name}}',
14
15
  failed: 'Oturum açılamadı',
15
16
  passwordRequired: 'Şifrenizi girin',
@@ -65,6 +65,7 @@ const common: Messages<typeof en> = {
65
65
  preferences: 'Tercihler',
66
66
  },
67
67
  actions: {
68
+ back: 'Geri',
68
69
  save: 'Kaydet',
69
70
  saveChanges: 'Değişiklikleri kaydet',
70
71
  cancel: 'Vazgeç',
@@ -12,7 +12,7 @@ const company: Messages<typeof en> = {
12
12
  description: 'Şirketinizin uygulamada ve e-postalarda nasıl görüneceği.',
13
13
  name: 'Şirket adı',
14
14
  legalName: 'Ticari unvan',
15
- legalNameHint: 'Tescil edildiği şekliyle, ör. “Acme Ticaret A.Ş.”',
15
+ legalNameHint: 'Resmî olarak tescil edildiği tam ad.',
16
16
  },
17
17
  tax: {
18
18
  title: 'Vergi bilgileri',
@@ -205,6 +205,8 @@ const company: Messages<typeof en> = {
205
205
  },
206
206
  },
207
207
  modules: {
208
+ emptyTitle: 'Henüz modül yok',
209
+ emptyDescription: 'Bu panele eklediğiniz özellikler burada listelenecek.',
208
210
  title: 'Modüller',
209
211
  description: 'Bu çalışma alanında kullanılabilen özellik alanları.',
210
212
  note: 'Şimdilik salt okunur. Modüller <code>src/app.config.ts</code> içinde açılır; arka uç bu ayarı sakladığında bu sayfa düzenlenebilir olacak.',
@@ -2,6 +2,11 @@ import type { Messages } from '@/core/i18n/types'
2
2
  import type en from '../en/dashboard'
3
3
 
4
4
  const dashboard: Messages<typeof en> = {
5
+ readyDescription: 'Paneliniz hazır.',
6
+ noModules: {
7
+ title: 'Henüz burada bir şey yok',
8
+ description: 'Bu panele eklediğiniz özellikler burada ve kenar çubuğunda görünecek.',
9
+ },
5
10
  greeting: {
6
11
  morning: 'Günaydın, {{name}}',
7
12
  afternoon: 'İyi günler, {{name}}',
@@ -0,0 +1,49 @@
1
+ import type { Messages } from '@/core/i18n/types'
2
+ import type en from '../en/setup'
3
+
4
+ const tr: Messages<typeof en> = {
5
+ title: 'Panelinizi kurun',
6
+ progress: 'Adım {{step}} / {{total}}',
7
+ next: 'Devam et',
8
+ finish: 'Kurulumu tamamla',
9
+ failed: 'Kurulum tamamlanamadı',
10
+ insecure: 'Hesap oluşturmak için paneli https veya localhost üzerinden açın.',
11
+ localNote: 'Henüz bir sunucu bağlı değil: bu hesap ve verileri yalnızca bu tarayıcıda saklanır. Yayına almadan önce bir sunucu bağlayın.',
12
+ company: {
13
+ title: 'Şirketiniz',
14
+ description: 'Bu ad panelin her yerinde görünür. Her şeyi daha sonra Ayarlar’dan değiştirebilirsiniz.',
15
+ name: 'Şirket adı',
16
+ language: 'Dil',
17
+ timeZone: 'Saat dilimi',
18
+ currency: 'Para birimi',
19
+ },
20
+ admin: {
21
+ title: 'Yönetici hesabınız',
22
+ description: 'Panele tam erişimi olan Sahip siz olacaksınız.',
23
+ firstName: 'Ad',
24
+ lastName: 'Soyad',
25
+ email: 'E-posta',
26
+ password: 'Şifre',
27
+ confirmPassword: 'Şifreyi doğrulayın',
28
+ passwordWeak: 'Bu şifre aşağıdaki kuralları karşılamıyor',
29
+ passwordMismatch: 'Şifreler eşleşmiyor',
30
+ },
31
+ branding: {
32
+ title: 'Görünümünüz',
33
+ description: 'Bir vurgu rengi seçin ve logo ekleyin. Logo isteğe bağlıdır.',
34
+ color: 'Vurgu rengi',
35
+ logo: 'Logo',
36
+ },
37
+ review: {
38
+ title: 'Bilgileri kontrol edin',
39
+ description: 'Her şey doğru mu?',
40
+ admin: 'Sahip',
41
+ },
42
+ done: {
43
+ title: 'Her şey hazır',
44
+ description: '{{name}} kullanıma hazır.',
45
+ open: 'Paneli aç',
46
+ },
47
+ }
48
+
49
+ export default tr
@@ -1,14 +1,10 @@
1
- import productsEn from './products/locales/en'
2
- import productsTr from './products/locales/tr'
3
1
  // fp:locale-imports
4
2
 
5
3
  /** Translations owned by modules; each key is the module's namespace. */
6
4
  export const moduleLocalesEn = {
7
- products: productsEn,
8
5
  // fp:locale-en
9
6
  }
10
7
 
11
8
  export const moduleLocalesTr: typeof moduleLocalesEn = {
12
- products: productsTr,
13
9
  // fp:locale-tr
14
10
  }
@@ -1,19 +1,17 @@
1
- import { appConfig } from '@/app.config'
2
- import type { AppModule } from '@/core/modules/types'
3
- import { productsModule } from './products'
4
- // fp:module-imports
5
-
6
- /**
7
- * Every module available to this template. Register new modules here, then
8
- * enable them in src/app.config.ts → `modules`.
9
- */
10
- export const moduleRegistry = {
11
- products: productsModule,
12
- // fp:module-registry
13
- } satisfies Record<string, AppModule>
14
-
15
- export type ModuleId = keyof typeof moduleRegistry
16
-
17
- export const enabledModules: AppModule[] = (Object.keys(moduleRegistry) as ModuleId[])
18
- .filter((id) => appConfig.modules[id])
19
- .map((id) => moduleRegistry[id])
1
+ import { appConfig } from '@/app.config'
2
+ import type { AppModule } from '@/core/modules/types'
3
+ // fp:module-imports
4
+
5
+ /**
6
+ * Every module in this panel. `npm run new-module` registers new ones here and
7
+ * enables them in src/app.config.ts → `modules`.
8
+ */
9
+ export const moduleRegistry = {
10
+ // fp:module-registry
11
+ } satisfies Record<string, AppModule>
12
+
13
+ export type ModuleId = keyof typeof moduleRegistry
14
+
15
+ export const enabledModules: AppModule[] = (Object.keys(moduleRegistry) as ModuleId[])
16
+ .filter((id) => appConfig.modules[id])
17
+ .map((id) => moduleRegistry[id])