@open-mercato/onboarding 0.4.2-canary-c02407ff85

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 (50) hide show
  1. package/build.mjs +62 -0
  2. package/dist/index.js +1 -0
  3. package/dist/index.js.map +7 -0
  4. package/dist/modules/onboarding/acl.js +11 -0
  5. package/dist/modules/onboarding/acl.js.map +7 -0
  6. package/dist/modules/onboarding/api/get/onboarding/verify.js +208 -0
  7. package/dist/modules/onboarding/api/get/onboarding/verify.js.map +7 -0
  8. package/dist/modules/onboarding/api/post/onboarding.js +193 -0
  9. package/dist/modules/onboarding/api/post/onboarding.js.map +7 -0
  10. package/dist/modules/onboarding/data/entities.js +84 -0
  11. package/dist/modules/onboarding/data/entities.js.map +7 -0
  12. package/dist/modules/onboarding/data/validators.js +27 -0
  13. package/dist/modules/onboarding/data/validators.js.map +7 -0
  14. package/dist/modules/onboarding/emails/AdminNotificationEmail.js +18 -0
  15. package/dist/modules/onboarding/emails/AdminNotificationEmail.js.map +7 -0
  16. package/dist/modules/onboarding/emails/VerificationEmail.js +36 -0
  17. package/dist/modules/onboarding/emails/VerificationEmail.js.map +7 -0
  18. package/dist/modules/onboarding/frontend/onboarding/page.js +279 -0
  19. package/dist/modules/onboarding/frontend/onboarding/page.js.map +7 -0
  20. package/dist/modules/onboarding/index.js +14 -0
  21. package/dist/modules/onboarding/index.js.map +7 -0
  22. package/dist/modules/onboarding/lib/service.js +83 -0
  23. package/dist/modules/onboarding/lib/service.js.map +7 -0
  24. package/dist/modules/onboarding/migrations/Migration20260112142945.js +12 -0
  25. package/dist/modules/onboarding/migrations/Migration20260112142945.js.map +7 -0
  26. package/generated/entities/onboarding_request/index.ts +19 -0
  27. package/generated/entities.ids.generated.ts +11 -0
  28. package/generated/entity-fields-registry.ts +11 -0
  29. package/jest.config.cjs +19 -0
  30. package/package.json +83 -0
  31. package/src/index.ts +2 -0
  32. package/src/modules/onboarding/acl.ts +7 -0
  33. package/src/modules/onboarding/api/get/onboarding/verify.ts +224 -0
  34. package/src/modules/onboarding/api/post/onboarding.ts +210 -0
  35. package/src/modules/onboarding/data/entities.ts +67 -0
  36. package/src/modules/onboarding/data/validators.ts +27 -0
  37. package/src/modules/onboarding/emails/AdminNotificationEmail.tsx +32 -0
  38. package/src/modules/onboarding/emails/VerificationEmail.tsx +54 -0
  39. package/src/modules/onboarding/frontend/onboarding/page.tsx +305 -0
  40. package/src/modules/onboarding/i18n/de.json +49 -0
  41. package/src/modules/onboarding/i18n/en.json +49 -0
  42. package/src/modules/onboarding/i18n/es.json +49 -0
  43. package/src/modules/onboarding/i18n/pl.json +49 -0
  44. package/src/modules/onboarding/index.ts +12 -0
  45. package/src/modules/onboarding/lib/service.ts +90 -0
  46. package/src/modules/onboarding/migrations/.snapshot-open-mercato.json +230 -0
  47. package/src/modules/onboarding/migrations/Migration20260112142945.ts +11 -0
  48. package/tsconfig.build.json +4 -0
  49. package/tsconfig.json +9 -0
  50. package/watch.mjs +6 -0
@@ -0,0 +1,27 @@
1
+ import { z } from 'zod'
2
+
3
+ export const onboardingStartSchema = z.object({
4
+ email: z.string().email(),
5
+ firstName: z.string().min(1).max(120),
6
+ lastName: z.string().min(1).max(120),
7
+ organizationName: z.string().min(1).max(240),
8
+ password: z.string().min(6).max(120),
9
+ confirmPassword: z.string().min(6).max(120),
10
+ termsAccepted: z.literal(true),
11
+ locale: z.string().min(2).max(10).optional(),
12
+ }).superRefine((value, ctx) => {
13
+ if (value.password !== value.confirmPassword) {
14
+ ctx.addIssue({
15
+ code: z.ZodIssueCode.custom,
16
+ message: 'Passwords must match.',
17
+ path: ['confirmPassword'],
18
+ })
19
+ }
20
+ })
21
+
22
+ export const onboardingVerifySchema = z.object({
23
+ token: z.string().min(32),
24
+ })
25
+
26
+ export type OnboardingStartInput = z.infer<typeof onboardingStartSchema>
27
+ export type OnboardingVerifyInput = z.infer<typeof onboardingVerifySchema>
@@ -0,0 +1,32 @@
1
+ import React from 'react'
2
+ import { Html, Head, Preview, Body, Container, Heading, Text, Hr } from '@react-email/components'
3
+
4
+ export type AdminNotificationCopy = {
5
+ preview: string
6
+ heading: string
7
+ body: string
8
+ footer: string
9
+ }
10
+
11
+ type AdminNotificationEmailProps = {
12
+ copy: AdminNotificationCopy
13
+ }
14
+
15
+ export default function AdminNotificationEmail({ copy }: AdminNotificationEmailProps) {
16
+ return (
17
+ <Html>
18
+ <Head>
19
+ <title>{copy.heading}</title>
20
+ </Head>
21
+ <Preview>{copy.preview}</Preview>
22
+ <Body style={{ backgroundColor: '#f8fafc', fontFamily: 'Helvetica, Arial, sans-serif', padding: '24px 0' }}>
23
+ <Container style={{ backgroundColor: '#ffffff', padding: '28px', borderRadius: '12px', margin: '0 auto', maxWidth: '520px' }}>
24
+ <Heading style={{ fontSize: '22px', fontWeight: 600, margin: '0 0 16px', color: '#0f172a' }}>{copy.heading}</Heading>
25
+ <Text style={{ fontSize: '15px', color: '#1f2937', lineHeight: '24px', marginBottom: '20px' }}>{copy.body}</Text>
26
+ <Hr style={{ borderColor: '#e2e8f0', margin: '24px 0' }} />
27
+ <Text style={{ fontSize: '13px', color: '#64748b' }}>{copy.footer}</Text>
28
+ </Container>
29
+ </Body>
30
+ </Html>
31
+ )
32
+ }
@@ -0,0 +1,54 @@
1
+ import React from 'react'
2
+ import { Html, Head, Preview, Body, Container, Heading, Text, Section, Button, Hr } from '@react-email/components'
3
+
4
+ export type VerificationEmailCopy = {
5
+ preview: string
6
+ heading: string
7
+ greeting: string
8
+ body: string
9
+ cta: string
10
+ expiry: string
11
+ footer: string
12
+ }
13
+
14
+ type VerificationEmailProps = {
15
+ verifyUrl: string
16
+ copy: VerificationEmailCopy
17
+ }
18
+
19
+ export default function VerificationEmail({ verifyUrl, copy }: VerificationEmailProps) {
20
+ return (
21
+ <Html>
22
+ <Head>
23
+ <title>{copy.heading}</title>
24
+ </Head>
25
+ <Preview>{copy.preview}</Preview>
26
+ <Body style={{ backgroundColor: '#f1f5f9', fontFamily: 'Helvetica, Arial, sans-serif', padding: '24px 0' }}>
27
+ <Container style={{ backgroundColor: '#ffffff', padding: '32px', borderRadius: '12px', margin: '0 auto', maxWidth: '520px' }}>
28
+ <Heading style={{ fontSize: '24px', fontWeight: 600, margin: '0 0 16px' }}>{copy.heading}</Heading>
29
+ <Text style={{ fontSize: '16px', color: '#334155', marginBottom: '16px' }}>{copy.greeting}</Text>
30
+ <Text style={{ fontSize: '16px', color: '#334155', marginBottom: '16px', lineHeight: '24px' }}>{copy.body}</Text>
31
+ <Section style={{ textAlign: 'center', margin: '32px 0' }}>
32
+ <Button
33
+ href={verifyUrl}
34
+ style={{
35
+ backgroundColor: '#111827',
36
+ color: '#ffffff',
37
+ padding: '12px 24px',
38
+ borderRadius: '8px',
39
+ fontSize: '15px',
40
+ textDecoration: 'none',
41
+ display: 'inline-block',
42
+ }}
43
+ >
44
+ {copy.cta}
45
+ </Button>
46
+ </Section>
47
+ <Text style={{ fontSize: '14px', color: '#64748b', marginBottom: '16px', lineHeight: '22px' }}>{copy.expiry}</Text>
48
+ <Hr style={{ borderColor: '#e2e8f0', margin: '24px 0' }} />
49
+ <Text style={{ fontSize: '12px', color: '#94a3b8' }}>{copy.footer}</Text>
50
+ </Container>
51
+ </Body>
52
+ </Html>
53
+ )
54
+ }
@@ -0,0 +1,305 @@
1
+ "use client"
2
+
3
+ import Image from 'next/image'
4
+ import { useState } from 'react'
5
+ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@open-mercato/ui/primitives/card'
6
+ import { Input } from '@open-mercato/ui/primitives/input'
7
+ import { Label } from '@open-mercato/ui/primitives/label'
8
+ import { Checkbox } from '@open-mercato/ui/primitives/checkbox'
9
+ import { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'
10
+ import { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'
11
+ import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
12
+ import { onboardingStartSchema } from '@open-mercato/onboarding/modules/onboarding/data/validators'
13
+
14
+ type SubmissionState = 'idle' | 'loading' | 'success'
15
+ type FieldErrors = Partial<Record<
16
+ 'email' | 'firstName' | 'lastName' | 'organizationName' | 'password' | 'confirmPassword' | 'termsAccepted',
17
+ string
18
+ >>
19
+
20
+ export default function OnboardingPage() {
21
+ const t = useT()
22
+ const translate = (key: string, fallback: string, params?: Record<string, string | number>) =>
23
+ translateWithFallback(t, key, fallback, params)
24
+ const locale = useLocale()
25
+ const [state, setState] = useState<SubmissionState>('idle')
26
+ const [globalError, setGlobalError] = useState<string | null>(null)
27
+ const [fieldErrors, setFieldErrors] = useState<FieldErrors>({})
28
+ const [termsAccepted, setTermsAccepted] = useState(false)
29
+ const [emailSubmitted, setEmailSubmitted] = useState<string | null>(null)
30
+
31
+ async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
32
+ event.preventDefault()
33
+ setState('loading')
34
+ setGlobalError(null)
35
+ setFieldErrors({})
36
+
37
+ const form = new FormData(event.currentTarget)
38
+ const payload = {
39
+ email: String(form.get('email') ?? '').trim(),
40
+ firstName: String(form.get('firstName') ?? '').trim(),
41
+ lastName: String(form.get('lastName') ?? '').trim(),
42
+ organizationName: String(form.get('organizationName') ?? '').trim(),
43
+ password: String(form.get('password') ?? ''),
44
+ confirmPassword: String(form.get('confirmPassword') ?? ''),
45
+ termsAccepted: termsAccepted,
46
+ locale,
47
+ }
48
+
49
+ const parsed = onboardingStartSchema.safeParse(payload)
50
+ if (!parsed.success) {
51
+ const issueMap: FieldErrors = {}
52
+ parsed.error.issues.forEach((issue) => {
53
+ const path = issue.path[0]
54
+ if (!path) return
55
+ switch (path) {
56
+ case 'email':
57
+ issueMap.email = translate('onboarding.errors.emailInvalid', 'Enter a valid work email.')
58
+ break
59
+ case 'firstName':
60
+ issueMap.firstName = translate('onboarding.errors.firstNameRequired', 'First name is required.')
61
+ break
62
+ case 'lastName':
63
+ issueMap.lastName = translate('onboarding.errors.lastNameRequired', 'Last name is required.')
64
+ break
65
+ case 'organizationName':
66
+ issueMap.organizationName = translate('onboarding.errors.organizationNameRequired', 'Organization name is required.')
67
+ break
68
+ case 'password':
69
+ issueMap.password = translate('onboarding.errors.passwordRequired', 'Password must be at least 6 characters.')
70
+ break
71
+ case 'confirmPassword':
72
+ issueMap.confirmPassword = translate('onboarding.errors.passwordMismatch', 'Passwords must match.')
73
+ break
74
+ case 'termsAccepted':
75
+ issueMap.termsAccepted = translate('onboarding.form.termsRequired', 'Please accept the terms to continue.')
76
+ break
77
+ default:
78
+ break
79
+ }
80
+ })
81
+ if (!issueMap.termsAccepted && !termsAccepted) {
82
+ issueMap.termsAccepted = translate('onboarding.form.termsRequired', 'Please accept the terms to continue.')
83
+ }
84
+ setFieldErrors(issueMap)
85
+ setState('idle')
86
+ return
87
+ }
88
+
89
+ try {
90
+ const call = await apiCall<{ ok?: boolean; error?: string; email?: string; fieldErrors?: Record<string, string> }>(
91
+ '/api/onboarding/onboarding',
92
+ {
93
+ method: 'POST',
94
+ headers: { 'content-type': 'application/json' },
95
+ body: JSON.stringify({ ...parsed.data, termsAccepted: true }),
96
+ },
97
+ )
98
+ const data = call.result ?? {}
99
+ if (!call.ok || data.ok === false) {
100
+ if (data.fieldErrors && typeof data.fieldErrors === 'object') {
101
+ const mapped: FieldErrors = {}
102
+ for (const key of Object.keys(data.fieldErrors)) {
103
+ const value = data.fieldErrors[key]
104
+ if (typeof value === 'string' && value.trim()) {
105
+ mapped[key as keyof FieldErrors] = value
106
+ }
107
+ }
108
+ setFieldErrors(mapped)
109
+ }
110
+ const message = typeof data.error === 'string' && data.error.trim()
111
+ ? data.error
112
+ : translate('onboarding.form.genericError', 'Something went wrong. Please try again.')
113
+ setGlobalError(message)
114
+ setState('idle')
115
+ return
116
+ }
117
+ setEmailSubmitted(data.email ?? parsed.data.email)
118
+ setState('success')
119
+ } catch (err) {
120
+ const message = err instanceof Error ? err.message : ''
121
+ setGlobalError(message || translate('onboarding.form.genericError', 'Something went wrong. Please try again.'))
122
+ setState('idle')
123
+ }
124
+ }
125
+
126
+ const submitting = state === 'loading'
127
+ const disabled = submitting || state === 'success'
128
+
129
+ return (
130
+ <div className="relative min-h-svh flex items-center justify-center bg-muted/40 px-4 pb-24">
131
+ <Card className="w-full max-w-lg shadow-lg">
132
+ <CardHeader className="flex flex-col gap-4 p-10 text-center">
133
+ <div className="flex flex-col items-center gap-3">
134
+ <Image alt="Open Mercato" src="/open-mercato.svg" width={120} height={120} priority />
135
+ <CardTitle className="text-2xl font-semibold">
136
+ {translate('onboarding.title', 'Create your Open Mercato workspace')}
137
+ </CardTitle>
138
+ <CardDescription>
139
+ {translate('onboarding.subtitle', 'Tell us a bit about you and we will set everything up.')}
140
+ </CardDescription>
141
+ </div>
142
+ </CardHeader>
143
+ <CardContent className="pb-10">
144
+ {state === 'success' && emailSubmitted && (
145
+ <div className="mb-6 rounded-md border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-900" role="status" aria-live="polite">
146
+ <strong className="block text-sm font-medium">
147
+ {translate('onboarding.form.successTitle', 'Check your inbox')}
148
+ </strong>
149
+ <p>
150
+ {translate('onboarding.form.successBody', 'We sent a verification link to {email}. Confirm it within 24 hours to activate your workspace.', { email: emailSubmitted })}
151
+ </p>
152
+ </div>
153
+ )}
154
+ {state !== 'success' && globalError && (
155
+ <div className="mb-4 rounded-md border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" role="alert" aria-live="assertive">
156
+ {globalError}
157
+ </div>
158
+ )}
159
+ <form className="grid gap-4" onSubmit={onSubmit} noValidate>
160
+ <div className="grid gap-1">
161
+ <Label htmlFor="email">{translate('onboarding.form.email', 'Work email')}</Label>
162
+ <Input
163
+ id="email"
164
+ name="email"
165
+ type="email"
166
+ required
167
+ disabled={disabled}
168
+ autoComplete="email"
169
+ aria-invalid={Boolean(fieldErrors.email)}
170
+ aria-describedby={fieldErrors.email ? 'email-error' : undefined}
171
+ className={fieldErrors.email ? 'border-red-500 focus-visible:ring-red-500' : undefined}
172
+ />
173
+ {fieldErrors.email && (
174
+ <p id="email-error" className="text-xs text-red-600">{fieldErrors.email}</p>
175
+ )}
176
+ </div>
177
+ <div className="grid gap-1 sm:grid-cols-2 sm:gap-4">
178
+ <div className="grid gap-1">
179
+ <Label htmlFor="firstName">{translate('onboarding.form.firstName', 'First name')}</Label>
180
+ <Input
181
+ id="firstName"
182
+ name="firstName"
183
+ type="text"
184
+ required
185
+ disabled={disabled}
186
+ autoComplete="given-name"
187
+ aria-invalid={Boolean(fieldErrors.firstName)}
188
+ aria-describedby={fieldErrors.firstName ? 'firstName-error' : undefined}
189
+ className={fieldErrors.firstName ? 'border-red-500 focus-visible:ring-red-500' : undefined}
190
+ />
191
+ {fieldErrors.firstName && (
192
+ <p id="firstName-error" className="text-xs text-red-600">{fieldErrors.firstName}</p>
193
+ )}
194
+ </div>
195
+ <div className="grid gap-1">
196
+ <Label htmlFor="lastName">{translate('onboarding.form.lastName', 'Last name')}</Label>
197
+ <Input
198
+ id="lastName"
199
+ name="lastName"
200
+ type="text"
201
+ required
202
+ disabled={disabled}
203
+ autoComplete="family-name"
204
+ aria-invalid={Boolean(fieldErrors.lastName)}
205
+ aria-describedby={fieldErrors.lastName ? 'lastName-error' : undefined}
206
+ className={fieldErrors.lastName ? 'border-red-500 focus-visible:ring-red-500' : undefined}
207
+ />
208
+ {fieldErrors.lastName && (
209
+ <p id="lastName-error" className="text-xs text-red-600">{fieldErrors.lastName}</p>
210
+ )}
211
+ </div>
212
+ </div>
213
+ <div className="grid gap-1">
214
+ <Label htmlFor="organizationName">{translate('onboarding.form.organizationName', 'Organization name')}</Label>
215
+ <Input
216
+ id="organizationName"
217
+ name="organizationName"
218
+ type="text"
219
+ required
220
+ disabled={disabled}
221
+ autoComplete="organization"
222
+ aria-invalid={Boolean(fieldErrors.organizationName)}
223
+ aria-describedby={fieldErrors.organizationName ? 'organizationName-error' : undefined}
224
+ className={fieldErrors.organizationName ? 'border-red-500 focus-visible:ring-red-500' : undefined}
225
+ />
226
+ {fieldErrors.organizationName && (
227
+ <p id="organizationName-error" className="text-xs text-red-600">{fieldErrors.organizationName}</p>
228
+ )}
229
+ </div>
230
+ <div className="grid gap-1">
231
+ <Label htmlFor="password">{translate('onboarding.form.password', 'Password')}</Label>
232
+ <Input
233
+ id="password"
234
+ name="password"
235
+ type="password"
236
+ required
237
+ disabled={disabled}
238
+ autoComplete="new-password"
239
+ aria-invalid={Boolean(fieldErrors.password)}
240
+ aria-describedby={fieldErrors.password ? 'password-error' : undefined}
241
+ className={fieldErrors.password ? 'border-red-500 focus-visible:ring-red-500' : undefined}
242
+ />
243
+ {fieldErrors.password && (
244
+ <p id="password-error" className="text-xs text-red-600">{fieldErrors.password}</p>
245
+ )}
246
+ </div>
247
+ <div className="grid gap-1">
248
+ <Label htmlFor="confirmPassword">{translate('onboarding.form.confirmPassword', 'Confirm password')}</Label>
249
+ <Input
250
+ id="confirmPassword"
251
+ name="confirmPassword"
252
+ type="password"
253
+ required
254
+ disabled={disabled}
255
+ autoComplete="new-password"
256
+ aria-invalid={Boolean(fieldErrors.confirmPassword)}
257
+ aria-describedby={fieldErrors.confirmPassword ? 'confirmPassword-error' : undefined}
258
+ className={fieldErrors.confirmPassword ? 'border-red-500 focus-visible:ring-red-500' : undefined}
259
+ />
260
+ {fieldErrors.confirmPassword && (
261
+ <p id="confirmPassword-error" className="text-xs text-red-600">{fieldErrors.confirmPassword}</p>
262
+ )}
263
+ </div>
264
+ <label className="flex items-start gap-3 text-sm text-muted-foreground">
265
+ <Checkbox
266
+ id="terms"
267
+ checked={termsAccepted}
268
+ disabled={disabled}
269
+ onCheckedChange={(value: boolean | 'indeterminate') => {
270
+ setTermsAccepted(value === true)
271
+ if (value === true) {
272
+ setFieldErrors((prev) => {
273
+ const next = { ...prev }
274
+ delete next.termsAccepted
275
+ return next
276
+ })
277
+ }
278
+ }}
279
+ aria-invalid={Boolean(fieldErrors.termsAccepted)}
280
+ />
281
+ <span>
282
+ {translate('onboarding.form.termsLabel', 'I have read and accept the terms of service')}{' '}
283
+ <a className="underline hover:text-foreground" href="/terms" target="_blank" rel="noreferrer">
284
+ {translate('onboarding.form.termsLink', 'terms of service')}
285
+ </a>
286
+ {fieldErrors.termsAccepted && (
287
+ <span className="mt-1 block text-xs text-red-600">{fieldErrors.termsAccepted}</span>
288
+ )}
289
+ </span>
290
+ </label>
291
+ <button
292
+ type="submit"
293
+ disabled={disabled}
294
+ className="mt-2 h-11 rounded-md bg-foreground text-background transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60"
295
+ >
296
+ {submitting
297
+ ? translate('onboarding.form.loading', 'Sending...')
298
+ : translate('onboarding.form.submit', 'Send verification email')}
299
+ </button>
300
+ </form>
301
+ </CardContent>
302
+ </Card>
303
+ </div>
304
+ )
305
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "onboarding": {
3
+ "title": "Create your Open Mercato workspace",
4
+ "subtitle": "Tell us a bit about you and we will set everything up.",
5
+ "form": {
6
+ "email": "Work email",
7
+ "firstName": "First name",
8
+ "lastName": "Last name",
9
+ "organizationName": "Organization name",
10
+ "password": "Password",
11
+ "confirmPassword": "Confirm password",
12
+ "termsLabel": "I have read and accept the terms of service",
13
+ "submit": "Send verification email",
14
+ "loading": "Sending...",
15
+ "successTitle": "Check your inbox",
16
+ "successBody": "We sent a verification link to {email}. Confirm it within 24 hours to activate your workspace.",
17
+ "emailExists": "We already have an account with this email. Try signing in or resetting your password.",
18
+ "genericError": "Something went wrong. Please try again or contact support.",
19
+ "termsLink": "terms of service",
20
+ "termsRequired": "Please accept the terms to continue."
21
+ },
22
+ "email": {
23
+ "subject": "Confirm your email to finish onboarding",
24
+ "preview": "Confirm your email to activate your Open Mercato workspace",
25
+ "heading": "Welcome to Open Mercato",
26
+ "greeting": "Hi {firstName},",
27
+ "body": "We just need to confirm your email address to finish setting up the organization {organizationName}.",
28
+ "cta": "Confirm email & activate workspace",
29
+ "expiry": "The link will expire in 24 hours. If you didn't request this, you can safely ignore this message.",
30
+ "footer": "Open Mercato · Tenant onboarding service",
31
+ "adminSubject": "New self-service onboarding request",
32
+ "adminPreview": "New onboarding request submitted",
33
+ "adminHeading": "New onboarding request",
34
+ "adminBody": "{firstName} {lastName} ({email}) submitted an onboarding request for {organizationName}.",
35
+ "adminFooter": "You can review the tenant after verification is complete."
36
+ },
37
+ "errors": {
38
+ "emailInvalid": "Enter a valid work email.",
39
+ "firstNameRequired": "First name is required.",
40
+ "lastNameRequired": "Last name is required.",
41
+ "organizationNameRequired": "Organization name is required.",
42
+ "passwordRequired": "Password must be at least 6 characters.",
43
+ "passwordMismatch": "Passwords must match.",
44
+ "termsRequired": "Please accept the terms to continue.",
45
+ "emailExists": "We already have an account with this email. Try signing in or resetting your password.",
46
+ "pendingRequest": "We already have a pending verification. Please try again in about {minutes} minutes or contact the administrator."
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "onboarding": {
3
+ "title": "Create your Open Mercato workspace",
4
+ "subtitle": "Tell us a bit about you and we will set everything up.",
5
+ "form": {
6
+ "email": "Work email",
7
+ "firstName": "First name",
8
+ "lastName": "Last name",
9
+ "organizationName": "Organization name",
10
+ "password": "Password",
11
+ "confirmPassword": "Confirm password",
12
+ "termsLabel": "I have read and accept the terms of service",
13
+ "submit": "Send verification email",
14
+ "loading": "Sending...",
15
+ "successTitle": "Check your inbox",
16
+ "successBody": "We sent a verification link to {email}. Confirm it within 24 hours to activate your workspace.",
17
+ "emailExists": "We already have an account with this email. Try signing in or resetting your password.",
18
+ "genericError": "Something went wrong. Please try again or contact support.",
19
+ "termsLink": "terms of service",
20
+ "termsRequired": "Please accept the terms to continue."
21
+ },
22
+ "email": {
23
+ "subject": "Confirm your email to finish onboarding",
24
+ "preview": "Confirm your email to activate your Open Mercato workspace",
25
+ "heading": "Welcome to Open Mercato",
26
+ "greeting": "Hi {firstName},",
27
+ "body": "We just need to confirm your email address to finish setting up the organization {organizationName}.",
28
+ "cta": "Confirm email & activate workspace",
29
+ "expiry": "The link will expire in 24 hours. If you didn't request this, you can safely ignore this message.",
30
+ "footer": "Open Mercato · Tenant onboarding service",
31
+ "adminSubject": "New self-service onboarding request",
32
+ "adminPreview": "New onboarding request submitted",
33
+ "adminHeading": "New onboarding request",
34
+ "adminBody": "{firstName} {lastName} ({email}) submitted an onboarding request for {organizationName}.",
35
+ "adminFooter": "You can review the tenant after verification is complete."
36
+ },
37
+ "errors": {
38
+ "emailInvalid": "Enter a valid work email.",
39
+ "firstNameRequired": "First name is required.",
40
+ "lastNameRequired": "Last name is required.",
41
+ "organizationNameRequired": "Organization name is required.",
42
+ "passwordRequired": "Password must be at least 6 characters.",
43
+ "passwordMismatch": "Passwords must match.",
44
+ "termsRequired": "Please accept the terms to continue.",
45
+ "emailExists": "We already have an account with this email. Try signing in or resetting your password.",
46
+ "pendingRequest": "We already have a pending verification. Please try again in about {minutes} minutes or contact the administrator."
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "onboarding": {
3
+ "title": "Create your Open Mercato workspace",
4
+ "subtitle": "Tell us a bit about you and we will set everything up.",
5
+ "form": {
6
+ "email": "Work email",
7
+ "firstName": "First name",
8
+ "lastName": "Last name",
9
+ "organizationName": "Organization name",
10
+ "password": "Password",
11
+ "confirmPassword": "Confirm password",
12
+ "termsLabel": "I have read and accept the terms of service",
13
+ "submit": "Send verification email",
14
+ "loading": "Sending...",
15
+ "successTitle": "Check your inbox",
16
+ "successBody": "We sent a verification link to {email}. Confirm it within 24 hours to activate your workspace.",
17
+ "emailExists": "We already have an account with this email. Try signing in or resetting your password.",
18
+ "genericError": "Something went wrong. Please try again or contact support.",
19
+ "termsLink": "terms of service",
20
+ "termsRequired": "Please accept the terms to continue."
21
+ },
22
+ "email": {
23
+ "subject": "Confirm your email to finish onboarding",
24
+ "preview": "Confirm your email to activate your Open Mercato workspace",
25
+ "heading": "Welcome to Open Mercato",
26
+ "greeting": "Hi {firstName},",
27
+ "body": "We just need to confirm your email address to finish setting up the organization {organizationName}.",
28
+ "cta": "Confirm email & activate workspace",
29
+ "expiry": "The link will expire in 24 hours. If you didn't request this, you can safely ignore this message.",
30
+ "footer": "Open Mercato · Tenant onboarding service",
31
+ "adminSubject": "New self-service onboarding request",
32
+ "adminPreview": "New onboarding request submitted",
33
+ "adminHeading": "New onboarding request",
34
+ "adminBody": "{firstName} {lastName} ({email}) submitted an onboarding request for {organizationName}.",
35
+ "adminFooter": "You can review the tenant after verification is complete."
36
+ },
37
+ "errors": {
38
+ "emailInvalid": "Enter a valid work email.",
39
+ "firstNameRequired": "First name is required.",
40
+ "lastNameRequired": "Last name is required.",
41
+ "organizationNameRequired": "Organization name is required.",
42
+ "passwordRequired": "Password must be at least 6 characters.",
43
+ "passwordMismatch": "Passwords must match.",
44
+ "termsRequired": "Please accept the terms to continue.",
45
+ "emailExists": "We already have an account with this email. Try signing in or resetting your password.",
46
+ "pendingRequest": "We already have a pending verification. Please try again in about {minutes} minutes or contact the administrator."
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "onboarding": {
3
+ "title": "Create your Open Mercato workspace",
4
+ "subtitle": "Tell us a bit about you and we will set everything up.",
5
+ "form": {
6
+ "email": "Work email",
7
+ "firstName": "First name",
8
+ "lastName": "Last name",
9
+ "organizationName": "Organization name",
10
+ "password": "Password",
11
+ "confirmPassword": "Confirm password",
12
+ "termsLabel": "I have read and accept the terms of service",
13
+ "submit": "Send verification email",
14
+ "loading": "Sending...",
15
+ "successTitle": "Check your inbox",
16
+ "successBody": "We sent a verification link to {email}. Confirm it within 24 hours to activate your workspace.",
17
+ "emailExists": "We already have an account with this email. Try signing in or resetting your password.",
18
+ "genericError": "Something went wrong. Please try again or contact support.",
19
+ "termsLink": "terms of service",
20
+ "termsRequired": "Please accept the terms to continue."
21
+ },
22
+ "email": {
23
+ "subject": "Confirm your email to finish onboarding",
24
+ "preview": "Confirm your email to activate your Open Mercato workspace",
25
+ "heading": "Welcome to Open Mercato",
26
+ "greeting": "Hi {firstName},",
27
+ "body": "We just need to confirm your email address to finish setting up the organization {organizationName}.",
28
+ "cta": "Confirm email & activate workspace",
29
+ "expiry": "The link will expire in 24 hours. If you didn't request this, you can safely ignore this message.",
30
+ "footer": "Open Mercato · Tenant onboarding service",
31
+ "adminSubject": "New self-service onboarding request",
32
+ "adminPreview": "New onboarding request submitted",
33
+ "adminHeading": "New onboarding request",
34
+ "adminBody": "{firstName} {lastName} ({email}) submitted an onboarding request for {organizationName}.",
35
+ "adminFooter": "You can review the tenant after verification is complete."
36
+ },
37
+ "errors": {
38
+ "emailInvalid": "Enter a valid work email.",
39
+ "firstNameRequired": "First name is required.",
40
+ "lastNameRequired": "Last name is required.",
41
+ "organizationNameRequired": "Organization name is required.",
42
+ "passwordRequired": "Password must be at least 6 characters.",
43
+ "passwordMismatch": "Passwords must match.",
44
+ "termsRequired": "Please accept the terms to continue.",
45
+ "emailExists": "We already have an account with this email. Try signing in or resetting your password.",
46
+ "pendingRequest": "We already have a pending verification. Please try again in about {minutes} minutes or contact the administrator."
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,12 @@
1
+ import type { ModuleInfo } from '@open-mercato/shared/modules/registry'
2
+
3
+ export const metadata: ModuleInfo = {
4
+ name: 'onboarding',
5
+ title: 'Onboarding',
6
+ version: '0.1.0',
7
+ description: 'Self-service tenant and organization onboarding flow.',
8
+ author: 'Open Mercato Team',
9
+ license: 'Proprietary',
10
+ }
11
+
12
+ export { features } from './acl'