Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
eec9f84
chore: added Astro's built i18n routing
JoblersTune Apr 8, 2025
8c8f582
TMP
JoblersTune Apr 8, 2025
79a5267
TMP
JoblersTune Apr 8, 2025
4dd0b43
feat: added Starlight's built in internationalization
JoblersTune Apr 8, 2025
95828bd
TMP
JoblersTune Apr 8, 2025
d05598c
TMP
JoblersTune Apr 8, 2025
b906491
chore: undo Starlight locale conflict. Can't be supported
JoblersTune Apr 8, 2025
58da964
TMP
JoblersTune Apr 8, 2025
8e9d88b
TMP
JoblersTune Apr 8, 2025
1803b84
TMP experiment
JoblersTune Apr 8, 2025
cebe4e1
TMP undo experiment
JoblersTune Apr 8, 2025
c807f04
chore: removed defaultLocale
JoblersTune Apr 10, 2025
16eac9a
chore: filtering blogs and building pages
JoblersTune Apr 11, 2025
130d33a
chore: added a lang attribute for filtering and all slugs must be unique
JoblersTune Apr 11, 2025
4f7f9eb
work in progress, dynamic routing to make language for all blog posts…
JoblersTune Apr 11, 2025
c8925d4
chore: add translation for nav items
Anca2022 Jul 2, 2025
e7e08d8
chore: add language switcher to navigation menu
Anca2022 Jul 2, 2025
2ab6b21
chore: set color for links in language switcher
Anca2022 Jul 3, 2025
992e7d6
chore: add translations for blog article paths
Anca2022 Jul 4, 2025
a2e4eb7
Merge pull request #141 from interledger/am/language-switcher
Anca2022 Oct 20, 2025
69cc115
fix social icons
JonathanMatthey Oct 20, 2025
292df49
fix social icons
JonathanMatthey Oct 20, 2025
8d63df4
feat: starlight docs pages feedback module
JonathanMatthey Oct 20, 2025
ec2f239
feat: add GitHub API integration for feedback widget
JonathanMatthey Oct 20, 2025
271f45b
fix: update issues repo
Nov 5, 2025
34a8299
fix: update feedback repo to open-payments-docs-feedback
JonathanMatthey Apr 21, 2026
33e72fb
Merge main into jm/starlightdocs-feedback
JonathanMatthey Apr 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# GitHub Personal Access Token for Feedback Widget
# Create a token at: https://github.com/settings/tokens/new
# Required scopes: public_repo (for public repositories)
GITHUB_TOKEN=ghp_your_github_token_here
30 changes: 30 additions & 0 deletions FEEDBACK_INTEGRATION_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Feedback Widget - GitHub Issues Integration

The feedback widget is **fully integrated with GitHub Issues**. When users submit feedback, it automatically creates an issue in your repository.

## 🚀 Quick Setup (5 minutes)

### Step 1: Create a GitHub Personal Access Token

1. Go to: https://github.com/settings/tokens/new
2. Give it a name: "Interledger Docs Feedback"
3. Select expiration (recommend: 90 days or No expiration)
4. Select scopes: **`public_repo`** (for public repositories)
5. Click "Generate token"
6. **Copy the token** (you won't see it again!)

### Step 2: Add Token to Environment Variables

Create a `.env` file in your project root:

```bash
cp .env.example .env
```

Edit `.env` and add your token:

```env
GITHUB_TOKEN=ghp_your_actual_token_here
```

⚠️ **Important:** Make sure `.env` is in your `.gitignore` (it should be by default)
4 changes: 3 additions & 1 deletion astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { PUBLISHED_RFC_SIDEBAR_ITEMS } from './src/data/rfcs.ts'

// https://astro.build/config
export default defineConfig({
output: 'server',
site: 'https://interledger.org',
base: '/developers',
integrations: [
Expand Down Expand Up @@ -53,7 +54,8 @@ export default defineConfig({
],
components: {
Header: './src/components/Header.astro',
PageSidebar: './src/components/PageSidebar.astro'
PageSidebar: './src/components/PageSidebar.astro',
Footer: './src/components/Footer.astro'
},
social: [
{
Expand Down
324 changes: 324 additions & 0 deletions src/components/FeedbackWidget.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,324 @@
---
// Simple feedback widget that works without external services
// Feedback can be sent via GitHub Issues or collected locally
const { lang = 'en' } = Astro.props;

const translations = {
en: {
question: "Was this page helpful?",
yes: "Yes",
no: "No",
thanks: "Thanks for your feedback!",
improve: "Help us improve",
improvePlaceholder: "What can we improve?",
submit: "Submit",
submitting: "Submitting...",
},
es: {
question: "¿Fue útil esta página?",
yes: "Sí",
no: "No",
thanks: "¡Gracias por tu comentario!",
improve: "Ayúdanos a mejorar",
improvePlaceholder: "¿Qué podemos mejorar?",
submit: "Enviar",
submitting: "Enviando...",
}
};

const t = translations[lang as keyof typeof translations] || translations.en;
---

<div class="feedback-widget" id="feedback-widget">
<div class="feedback-question">
<h3>{t.question}</h3>
<div class="feedback-buttons">
<button class="feedback-btn feedback-yes" data-feedback="yes">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 1.5L12.5 6.5L18 7.5L14 11.5L15 17L10 14.5L5 17L6 11.5L2 7.5L7.5 6.5L10 1.5Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
{t.yes}
</button>
<button class="feedback-btn feedback-no" data-feedback="no">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 5L5 15M5 5L15 15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
{t.no}
</button>
</div>
</div>

<div class="feedback-form hidden" id="feedback-form">
<p class="feedback-thanks">{t.thanks}</p>
<label for="feedback-text">{t.improve}</label>
<textarea
id="feedback-text"
rows="4"
placeholder={t.improvePlaceholder}
></textarea>
<button class="feedback-submit" id="submit-feedback">
{t.submit}
</button>
</div>

<div class="feedback-success hidden" id="feedback-success">
<p>{t.thanks}</p>
</div>
</div>

<style>
.feedback-widget {
margin-top: var(--sl-spacing-2xl, 2rem);
padding: var(--sl-spacing-m, 1rem);
border-top: 1px solid var(--sl-color-hairline-shade, #e0e0e0);
}

.feedback-question h3 {
font-size: var(--sl-text-base, 1rem);
font-weight: 600;
margin: 0 0 var(--sl-spacing-s, 0.75rem) 0;
color: var(--sl-color-text, #333);
}

.feedback-buttons {
display: flex;
gap: var(--sl-spacing-xs, 0.5rem);
}

.feedback-btn {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border: 1px solid var(--sl-color-hairline-shade, #e0e0e0);
background: var(--sl-color-bg, white);
color: var(--sl-color-text, #333);
border-radius: 0.375rem;
cursor: pointer;
font-size: 0.875rem;
transition: all 0.2s;
}

.feedback-btn:hover {
background: var(--sl-color-bg-nav, #f5f5f5);
border-color: var(--sl-color-text-accent, #0066cc);
}

.feedback-btn svg {
flex-shrink: 0;
}

.feedback-yes:hover {
color: #16a34a;
border-color: #16a34a;
}

.feedback-no:hover {
color: #dc2626;
border-color: #dc2626;
}

.feedback-form {
margin-top: var(--sl-spacing-m, 1rem);
}

.feedback-form label {
display: block;
margin-bottom: var(--sl-spacing-2xs, 0.25rem);
font-size: 0.875rem;
font-weight: 500;
color: var(--sl-color-text, #333);
}

.feedback-form textarea {
width: 100%;
padding: 0.5rem;
border: 1px solid var(--sl-color-hairline-shade, #e0e0e0);
border-radius: 0.375rem;
font-family: inherit;
font-size: 0.875rem;
resize: vertical;
background: var(--sl-color-bg, white);
color: var(--sl-color-text, #333);
}

.feedback-form textarea:focus {
outline: none;
border-color: var(--sl-color-text-accent, #0066cc);
box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1);
}

.feedback-submit {
margin-top: var(--sl-spacing-xs, 0.5rem);
padding: 0.5rem 1rem;
background: var(--sl-color-text-accent, #0066cc);
color: white;
border: none;
border-radius: 0.375rem;
cursor: pointer;
font-size: 0.875rem;
font-weight: 500;
transition: background 0.2s;
}

.feedback-submit:hover {
background: var(--sl-color-text-accent-high, #0052a3);
}

.feedback-submit:disabled {
opacity: 0.5;
cursor: not-allowed;
}

.feedback-thanks {
color: #16a34a;
font-weight: 500;
margin-bottom: var(--sl-spacing-xs, 0.5rem);
}

.feedback-success {
color: #16a34a;
font-weight: 500;
padding: var(--sl-spacing-m, 1rem);
text-align: center;
}

.hidden {
display: none;
}
</style>

<script>
const widget = document.getElementById('feedback-widget');
const feedbackBtns = widget?.querySelectorAll('.feedback-btn');
const feedbackForm = document.getElementById('feedback-form');
const feedbackSuccess = document.getElementById('feedback-success');
const submitBtn = document.getElementById('submit-feedback');
const feedbackText = document.getElementById('feedback-text') as HTMLTextAreaElement;

let selectedFeedback: string | null = null;

feedbackBtns?.forEach(btn => {
btn.addEventListener('click', () => {
selectedFeedback = btn.getAttribute('data-feedback');

// Hide buttons, show form
document.querySelector('.feedback-question')?.classList.add('hidden');
feedbackForm?.classList.remove('hidden');

// If positive feedback, user can skip writing
if (selectedFeedback === 'yes') {
feedbackText.placeholder = feedbackText.placeholder;
}
});
});

submitBtn?.addEventListener('click', async () => {
const feedback = feedbackText.value.trim();
const page = window.location.pathname;

// Disable button while submitting
submitBtn.disabled = true;
const originalText = submitBtn.textContent;
submitBtn.textContent = submitBtn.getAttribute('data-submitting') || 'Submitting...';

try {
// Send to GitHub Issues API with timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout

const response = await fetch('/developers/api/feedback', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: selectedFeedback,
page: page,
message: feedback,
}),
signal: controller.signal
});

clearTimeout(timeoutId);

const result = await response.json();

if (!response.ok || !result.success) {
// If GitHub token not configured, fall back to opening GitHub issue page
if (result.error?.includes('not configured') || result.error?.includes('token')) {
const emoji = selectedFeedback === 'yes' ? '👍' : '👎';
const title = encodeURIComponent(`[Feedback] ${emoji} ${page}`);
const body = encodeURIComponent(`**Page:** ${page}
**Feedback Type:** ${selectedFeedback === 'yes' ? 'Positive 👍' : 'Negative 👎'}
**User Message:**

${feedback || '_No additional feedback provided_'}`);

const issueUrl = `https://github.com/interledger/documentation-feedback/issues/new?title=${title}&body=${body}&labels=feedback,docs`;

window.open(issueUrl, '_blank');

feedbackForm?.classList.add('hidden');
feedbackSuccess?.classList.remove('hidden');
return;
}

throw new Error(result.error || 'Failed to submit feedback');
}

// Log success
console.log('Feedback submitted successfully:', {
issueNumber: result.issueNumber,
issueUrl: result.issueUrl
});

// Show success message
feedbackForm?.classList.add('hidden');
feedbackSuccess?.classList.remove('hidden');

// Track with Umami if available
if (typeof window !== 'undefined' && (window as any).umami) {
(window as any).umami.track('feedback', {
type: selectedFeedback,
hasMessage: feedback.length > 0,
success: true
});
}

} catch (error) {
console.error('Error submitting feedback:', error);

// Re-enable button
submitBtn.disabled = false;
submitBtn.textContent = originalText;

// Show error message
const errorMsg = error instanceof Error && error.name === 'AbortError'
? 'Request timed out. Opening GitHub issue page instead...'
: 'Failed to submit feedback. Opening GitHub issue page...';

alert(errorMsg);

// Fallback: Open GitHub issue page
const emoji = selectedFeedback === 'yes' ? '👍' : '👎';
const title = encodeURIComponent(`[Feedback] ${emoji} ${page}`);
const body = encodeURIComponent(`**Page:** ${page}
**Feedback Type:** ${selectedFeedback === 'yes' ? 'Positive 👍' : 'Negative 👎'}
**User Message:**

${feedback || '_No additional feedback provided_'}`);

const issueUrl = `https://github.com/interledger/documentation-feedback/issues/new?title=${title}&body=${body}&labels=feedback,docs`;
window.open(issueUrl, '_blank');

// Track error with Umami if available
if (typeof window !== 'undefined' && (window as any).umami) {
(window as any).umami.track('feedback-error', {
type: selectedFeedback,
error: error instanceof Error ? error.message : 'Unknown error'
});
}
}
});
</script>
12 changes: 12 additions & 0 deletions src/components/Footer.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
import type { Props } from '@astrojs/starlight/props';
import Default from '@astrojs/starlight/components/Footer.astro';
import FeedbackWidget from './FeedbackWidget.astro';
import { getLangFromUrl } from '../i18n/utils';

const lang = getLangFromUrl(Astro.url);
---

<FeedbackWidget lang={lang} />

<Default {...Astro.props}><slot /></Default>
Loading
Loading