Skip to content

Commit e7b62e9

Browse files
committed
feat(providers): add ability to copy refresh token
Adds a button to copy the refresh token for each provider directly from the UI. This allows users to easily retrieve their refresh tokens for debugging or other uses. Introduces a `copyRefreshToken` function in the presenter to handle the logic. Adds a new API endpoint to download the content of an authentication file. Updates the `AuthFile` type to include an optional `name` property. Adds corresponding translation keys for the new functionality.
1 parent bb94269 commit e7b62e9

11 files changed

Lines changed: 65 additions & 17 deletions

File tree

src/features/providers/ProvidersPage.tsx

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import {
1515
Eye,
1616
EyeOff,
1717
ChevronDown,
18-
ChevronRight
18+
ChevronRight,
19+
Key
1920
} from 'lucide-react';
2021
import {
2122
AlertDialog,
@@ -66,6 +67,7 @@ export function ProvidersPage() {
6667
executeDeleteAll,
6768
showDeleteAllConfirmation,
6869
setShowDeleteAllConfirmation,
70+
copyRefreshToken,
6971
} = useProvidersPresenter();
7072

7173
if (!isAuthenticated) {
@@ -262,15 +264,26 @@ export function ProvidersPage() {
262264
</div>
263265
</div>
264266

265-
<Button
266-
size="icon"
267-
variant="ghost"
268-
className="h-8 w-8 text-destructive hover:bg-destructive/10 opacity-80 group-hover:opacity-100"
269-
onClick={() => setFileToDelete(file.id)}
270-
title={t('common.delete')}
271-
>
272-
<Trash2 className="h-4 w-4" />
273-
</Button>
267+
<div className="flex items-center gap-1">
268+
<Button
269+
size="icon"
270+
variant="ghost"
271+
className="h-8 w-8 text-primary hover:bg-primary/10 opacity-80 group-hover:opacity-100"
272+
onClick={() => copyRefreshToken(file.name || file.filename || file.id)}
273+
title={t('common.copyRefreshToken', 'Copy Refresh Token')}
274+
>
275+
<Key className="h-4 w-4" />
276+
</Button>
277+
<Button
278+
size="icon"
279+
variant="ghost"
280+
className="h-8 w-8 text-destructive hover:bg-destructive/10 opacity-80 group-hover:opacity-100"
281+
onClick={() => setFileToDelete(file.id)}
282+
title={t('common.delete')}
283+
>
284+
<Trash2 className="h-4 w-4" />
285+
</Button>
286+
</div>
274287
</div>
275288
);
276289
})}

src/features/providers/useProvidersPresenter.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,30 @@ export function useProvidersPresenter() {
347347
} catch { /* ignore */ }
348348
}, [t]);
349349

350+
const copyRefreshToken = useCallback(async (filename: string | undefined | null) => {
351+
try {
352+
if (!filename) {
353+
toast.error('Filename is missing');
354+
return;
355+
}
356+
let name = filename;
357+
if (!name.toLowerCase().endsWith('.json')) {
358+
name = `${name}.json`;
359+
}
360+
const data = await authFilesApi.download(name);
361+
const refreshToken = data.refresh_token;
362+
363+
if (refreshToken) {
364+
await navigator.clipboard.writeText(refreshToken);
365+
toast.success(t('common.copied') || 'Copied to clipboard!');
366+
} else {
367+
toast.error('No refresh token found in this file');
368+
}
369+
} catch (err) {
370+
toast.error(`Failed to copy refresh token: ${(err as Error).message}`);
371+
}
372+
}, [t]);
373+
350374
const togglePrivacyMode = useCallback(() => {
351375
setIsPrivacyMode(prev => !prev);
352376
}, []);
@@ -386,6 +410,7 @@ export function useProvidersPresenter() {
386410
submitCallback,
387411
updateProviderState,
388412
copyToClipboard,
413+
copyRefreshToken,
389414

390415
// Privacy
391416
isPrivacyMode,

src/i18n/locales/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"upload": "Upload",
1818
"deleteAll": "Delete All",
1919
"deleting": "Deleting...",
20-
"deleteWarning": "This action cannot be undone. This will permanently delete your account connection."
20+
"deleteWarning": "This action cannot be undone. This will permanently delete your account connection.",
21+
"copyRefreshToken": "Copy Refresh Token"
2122
},
2223
"auth": {
2324
"login": "Login",

src/i18n/locales/id.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"upload": "Unggah",
1818
"deleteAll": "Hapus Semua",
1919
"deleting": "Menghapus...",
20-
"deleteWarning": "Tindakan ini tidak dapat dibatalkan. Ini akan menghapus koneksi akun Anda secara permanen."
20+
"deleteWarning": "Tindakan ini tidak dapat dibatalkan. Ini akan menghapus koneksi akun Anda secara permanen.",
21+
"copyRefreshToken": "Salin Token Penyegaran"
2122
},
2223
"auth": {
2324
"login": "Masuk",

src/i18n/locales/ja.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"upload": "アップロード",
1818
"deleteAll": "すべて削除",
1919
"deleting": "削除中...",
20-
"deleteWarning": "この操作は取り消せません。アカウント接続が完全に削除されます。"
20+
"deleteWarning": "この操作は取り消せません。アカウント接続が完全に削除されます。",
21+
"copyRefreshToken": "リフレッシュトークンをコピー"
2122
},
2223
"auth": {
2324
"login": "ログイン",

src/i18n/locales/ko.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"upload": "업로드",
1818
"deleteAll": "모두 삭제",
1919
"deleting": "삭제 중...",
20-
"deleteWarning": "이 작업은 취소할 수 없습니다. 계정 연결이 영구적으로 삭제됩니다."
20+
"deleteWarning": "이 작업은 취소할 수 없습니다. 계정 연결이 영구적으로 삭제됩니다.",
21+
"copyRefreshToken": "갱신 토큰 복사"
2122
},
2223
"auth": {
2324
"login": "로그인",

src/i18n/locales/th.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"upload": "อัปโหลด",
1818
"deleteAll": "ลบทั้งหมด",
1919
"deleting": "กำลังลบ...",
20-
"deleteWarning": "การดำเนินการนี้ไม่สามารถยกเลิกได้ การเชื่อมต่อบัญชีของคุณจะถูกลบอย่างถาวร"
20+
"deleteWarning": "การดำเนินการนี้ไม่สามารถยกเลิกได้ การเชื่อมต่อบัญชีของคุณจะถูกลบอย่างถาวร",
21+
"copyRefreshToken": "คัดลอกโทเค็นรีเฟรช"
2122
},
2223
"auth": {
2324
"login": "เข้าสู่ระบบ",

src/i18n/locales/vi.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"upload": "Tải lên",
1818
"deleteAll": "Xóa tất cả",
1919
"deleting": "Đang xóa...",
20-
"deleteWarning": "Hành động này không thể hoàn tác. Việc này sẽ xóa vĩnh viễn kết nối tài khoản của bạn."
20+
"deleteWarning": "Hành động này không thể hoàn tác. Việc này sẽ xóa vĩnh viễn kết nối tài khoản của bạn.",
21+
"copyRefreshToken": "Sao chép Token làm mới"
2122
},
2223
"auth": {
2324
"login": "Đăng nhập",

src/i18n/locales/zh-CN.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"upload": "上传",
1818
"deleteAll": "全部删除",
1919
"deleting": "正在删除...",
20-
"deleteWarning": "此操作无法撤销。这将永久删除您的帐户连接。"
20+
"deleteWarning": "此操作无法撤销。这将永久删除您的帐户连接。",
21+
"copyRefreshToken": "复制刷新令牌"
2122
},
2223
"auth": {
2324
"login": "登录",

src/services/api/auth.service.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ export const authFilesApi = {
1111
deleteFile: (name: string) => apiClient.delete(`/auth-files?name=${encodeURIComponent(name)}`),
1212

1313
deleteAll: () => apiClient.delete('/auth-files?all=true'),
14+
15+
download: (name: string) => apiClient.get<any>(`/auth-files/download?name=${encodeURIComponent(name)}`),
1416
};

0 commit comments

Comments
 (0)