Skip to content

Commit 770eeec

Browse files
nedimfclaude
andcommitted
Add surveys & contact forms support (v0.1.2)
Add survey rendering with branching logic (6 question types) and contact form support with validation and rate limiting. Includes types, client methods, hooks, and UI components. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent fc54a7c commit 770eeec

20 files changed

Lines changed: 1637 additions & 51 deletions

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@appgram/react",
3-
"version": "0.1.1",
3+
"version": "0.1.2",
44
"description": "React library for integrating Appgram portal features with pre-built UI components and headless hooks",
55
"main": "dist/index.js",
66
"module": "dist/index.mjs",

src/client/AppgramClient.ts

Lines changed: 61 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ import type {
2121
HelpArticle,
2222
SupportRequest,
2323
SupportRequestInput,
24+
Survey,
25+
SurveyNode,
26+
SurveyResponse,
27+
SurveySubmitInput,
28+
ContactForm,
29+
ContactFormSubmission,
30+
ContactFormSubmitInput,
2431
} from '../types'
2532

2633
export interface AppgramClientConfig {
@@ -612,48 +619,65 @@ export class AppgramClient {
612619
token: string,
613620
content: string
614621
): Promise<ApiResponse<{ id: string; content: string; created_at: string }>> {
615-
const url = `/portal/support-requests/${ticketId}/messages`
616-
const fullUrl = `${this.baseUrl}${url}`
622+
return this.post<{ id: string; content: string; created_at: string }>(
623+
`/portal/support-requests/${ticketId}/messages?token=${encodeURIComponent(token)}`,
624+
{ content }
625+
)
626+
}
617627

618-
try {
619-
const response = await fetch(fullUrl, {
620-
method: 'POST',
621-
headers: {
622-
'Content-Type': 'application/json',
623-
'Authorization': `Bearer ${token}`,
624-
},
625-
body: JSON.stringify({ content }),
626-
})
628+
// ============================================================================
629+
// Surveys
630+
// ============================================================================
627631

628-
const data = await response.json()
632+
/**
633+
* Get a public survey by slug
634+
*/
635+
async getPublicSurvey(slug: string): Promise<ApiResponse<Survey & { nodes: SurveyNode[] }>> {
636+
return this.get<Survey & { nodes: SurveyNode[] }>(`/portal/surveys/${slug}`, {
637+
project_id: this.projectId,
638+
})
639+
}
629640

630-
if (!response.ok) {
631-
return {
632-
success: false,
633-
error: {
634-
code: String(response.status),
635-
message: data.message || data.error || 'An error occurred',
636-
},
637-
}
638-
}
641+
/**
642+
* Submit a survey response
643+
*/
644+
async submitSurveyResponse(
645+
surveyId: string,
646+
data: SurveySubmitInput
647+
): Promise<ApiResponse<SurveyResponse>> {
648+
return this.post<SurveyResponse>(`/portal/surveys/${surveyId}/responses`, data)
649+
}
639650

640-
if (data && typeof data === 'object' && 'success' in data) {
641-
return data
642-
}
651+
/**
652+
* Get survey customization settings
653+
*/
654+
async getPublicSurveyCustomization(surveyId: string): Promise<ApiResponse<Record<string, unknown>>> {
655+
return this.get<Record<string, unknown>>(`/portal/surveys/customization/${surveyId}`)
656+
}
643657

644-
return {
645-
success: true,
646-
data: data as { id: string; content: string; created_at: string },
647-
}
648-
} catch (error) {
649-
return {
650-
success: false,
651-
error: {
652-
code: 'NETWORK_ERROR',
653-
message: error instanceof Error ? error.message : 'Network error',
654-
},
655-
}
656-
}
658+
// ============================================================================
659+
// Contact Forms
660+
// ============================================================================
661+
662+
/**
663+
* Get a public contact form by ID
664+
*/
665+
async getPublicForm(formId: string): Promise<ApiResponse<ContactForm>> {
666+
return this.get<ContactForm>(`/api/v1/forms/${formId}`)
667+
}
668+
669+
/**
670+
* Submit a contact form
671+
*/
672+
async submitContactForm(
673+
projectId: string,
674+
formId: string,
675+
data: ContactFormSubmitInput
676+
): Promise<ApiResponse<ContactFormSubmission>> {
677+
return this.post<ContactFormSubmission>(
678+
`/api/v1/projects/${projectId}/contact-forms/${formId}/submit`,
679+
data
680+
)
657681
}
658682

659683
// ============================================================================

src/components/feedback/SubmitWishForm.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,6 @@ export function SubmitWishForm({
232232
setFormData({ ...formData, title: e.target.value })
233233
}
234234
required
235-
maxLength={150}
236235
className="w-full h-11 px-4 text-sm border focus:outline-none focus:ring-2 transition-all"
237236
style={{
238237
borderColor: isDark ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.15)',
@@ -277,7 +276,6 @@ export function SubmitWishForm({
277276
setFormData({ ...formData, description: e.target.value })
278277
}
279278
required
280-
maxLength={500}
281279
rows={5}
282280
className="w-full px-4 py-3 text-sm border focus:outline-none focus:ring-2 resize-none transition-all"
283281
style={{

src/components/feedback/WishDetail.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,6 @@ export function WishDetail({
404404
placeholder="Your name (optional)"
405405
value={authorName}
406406
onChange={(e) => setAuthorName(e.target.value)}
407-
maxLength={100}
408407
className="w-full h-10 px-4 text-sm border focus:outline-none focus:ring-2 transition-all"
409408
style={{
410409
borderColor: isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)',
@@ -418,7 +417,6 @@ export function WishDetail({
418417
placeholder="Write a comment..."
419418
value={newComment}
420419
onChange={(e) => setNewComment(e.target.value)}
421-
maxLength={1000}
422420
className="flex-1 min-h-[60px] px-4 py-3 text-sm border focus:outline-none focus:ring-2 resize-none transition-all"
423421
style={{
424422
borderColor: isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)',

0 commit comments

Comments
 (0)