diff --git a/.github/ISSUE_TEMPLATE/doc-feedback.yml b/.github/ISSUE_TEMPLATE/doc-feedback.yml new file mode 100644 index 0000000000..5ba5b9e7f4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/doc-feedback.yml @@ -0,0 +1,27 @@ +name: Documentation feedback +description: Feedback submitted from the docs.strapi.io feedback widget +labels: ["feedback: from-docs-widget"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to give us feedback! + The fields below are pre-filled from the widget. Edit them if needed. + - type: input + id: page + attributes: + label: Page + description: Which doc page is this about? + validations: + required: true + - type: textarea + id: selection + attributes: + label: Selected text or section + description: The specific part of the page you selected (if any) + - type: textarea + id: comment + attributes: + label: Your feedback + validations: + required: true diff --git a/.github/workflows/auto-respond-issues.yml b/.github/workflows/auto-respond-issues.yml index 69acc67f64..b047820e0d 100644 --- a/.github/workflows/auto-respond-issues.yml +++ b/.github/workflows/auto-respond-issues.yml @@ -71,7 +71,15 @@ jobs: console.log('Issue from maintainer, skipping auto-response'); return; } - + + // Skip if issue has a label that should bypass auto-response + const skipLabels = ['feedback: from-docs-widget']; + const issueLabels = (issue.labels || []).map(l => typeof l === 'string' ? l : l.name); + if (issueLabels.some(label => skipLabels.includes(label))) { + console.log('Issue has skip label, skipping auto-response'); + return; + } + // Prepare the enhanced query for Kapa AI const cleanedBody = issueBody .replace(//g, '') // Remove HTML comments diff --git a/docusaurus/docs/cms/usage-information.md b/docusaurus/docs/cms/usage-information.md index 9afaec1138..c259119088 100644 --- a/docusaurus/docs/cms/usage-information.md +++ b/docusaurus/docs/cms/usage-information.md @@ -83,6 +83,20 @@ Data collection can later be re-enabled by deleting the flag or setting it to fa If you have any questions or concerns regarding data collection, please contact us at the following email address [privacy@strapi.io](mailto:privacy@strapi.io). ::: +## Documentation feedback widget {#documentation-feedback-widget} + +The documentation website at [docs.strapi.io](https://docs.strapi.io) includes a feedback widget at the bottom of each page. When you submit feedback, the following data is collected: + +- Your vote (positive or negative) +- Your comment (if provided) +- The page URL and title +- Your browser's user agent string (for debugging purposes) +- Your country (inferred from your IP address by Vercel; the IP address itself is not stored) + +This data is stored in a private Notion database accessible only to the Strapi documentation team. No email address, name, or other personally identifiable information is collected. Feedback is anonymous and cannot be traced back to individual users. + +For questions about feedback data collection, contact [privacy@strapi.io](mailto:privacy@strapi.io). + ## Strapi AI data handling {#strapi-ai-data-handling} [Strapi AI features](/cms/ai/for-content-managers) process requests through Strapi-managed infrastructure. Temporary metadata and content snippets exist only for the duration of each request. Strapi never stores unpublished content or credentials outside your instance. diff --git a/docusaurus/src/components/PageFeedback/FeedbackForm.jsx b/docusaurus/src/components/PageFeedback/FeedbackForm.jsx new file mode 100644 index 0000000000..79a0eea00c --- /dev/null +++ b/docusaurus/src/components/PageFeedback/FeedbackForm.jsx @@ -0,0 +1,82 @@ +import React, { useState } from 'react'; + +const MIN_COMMENT_LENGTH = 20; +const MAX_COMMENT_LENGTH = 2000; + +export default function FeedbackForm({ + onSubmit, + onCancel, + isSubmitting, + required, +}) { + const [comment, setComment] = useState(''); + + const trimmed = comment.trim(); + const isTooLong = trimmed.length > MAX_COMMENT_LENGTH; + const canSubmit = required + ? trimmed.length >= MIN_COMMENT_LENGTH && !isTooLong && !isSubmitting + : !isTooLong && !isSubmitting; + + function handleSubmit(e) { + e.preventDefault(); + if (!canSubmit) return; + const hp = e.target.elements._hp?.value || ''; + onSubmit(trimmed || null, hp); + } + + return ( +
+ +