diff --git a/README.md b/README.md
index 09ef4cb..ed9280f 100644
--- a/README.md
+++ b/README.md
@@ -117,6 +117,7 @@ flowchart LR
(`GameAchievements`, grouped by `achievementSets`). A trophy has its type (bronze to platinum), rarity and a DLC tag;
a hidden trophy shows "Hidden trophy" until it is earned; a set whose list is not stored yet says it loads with the
next sync. The sync banner and inbox text say "achievements or trophies". Library tiles show the best set of a game.
+ The sync details list games whose last achievement this sync unlocked, with a button to set them to 100 %.
- **Game detail**: status, priority, 1–5 star rating, notes, achievements, time to beat (story / extras / 100 %),
a link to the game's speedrun.com page when it has verified runs, a hint when Steam shows recent playtime for a
game still in the backlog, and one to set the status to 100 % when
diff --git a/public/i18n/de.json b/public/i18n/de.json
index 44de362..155187f 100644
--- a/public/i18n/de.json
+++ b/public/i18n/de.json
@@ -1335,7 +1335,9 @@
"CLOSE": "Schließen",
"EMPTY": "Keine Änderungen zum Anzeigen.",
"ERROR": "Die Änderungen konnten nicht geladen werden.",
+ "COMPLETED_DONE": "Auf 100 % gesetzt",
"HEADING": {
+ "ALL_UNLOCKED": "Alle Erfolge freigeschaltet",
"ADDED": "Neue Spiele",
"REMOVED": "Entfernte Spiele",
"PLAYTIME": "Spielzeit",
diff --git a/public/i18n/en.json b/public/i18n/en.json
index 4812e79..be15036 100644
--- a/public/i18n/en.json
+++ b/public/i18n/en.json
@@ -1335,7 +1335,9 @@
"CLOSE": "Close",
"EMPTY": "No changes to show.",
"ERROR": "The changes could not be loaded.",
+ "COMPLETED_DONE": "Set to 100%",
"HEADING": {
+ "ALL_UNLOCKED": "All achievements unlocked",
"ADDED": "New games",
"REMOVED": "Removed games",
"PLAYTIME": "Playtime",
diff --git a/src/components/SyncChangesBanner.jsx b/src/components/SyncChangesBanner.jsx
index 9d0f6f9..2b7e8d3 100644
--- a/src/components/SyncChangesBanner.jsx
+++ b/src/components/SyncChangesBanner.jsx
@@ -16,8 +16,9 @@ const keepShown = (prev, body, markedAt) =>
* device. A manual sync without changes says so once.
*
* @param lastSyncedAt the end of the last sync: a new value loads the result again
+ * @param onChanged called after a game was set to COMPLETED from the details
*/
-export default function SyncChangesBanner({lastSyncedAt}) {
+export default function SyncChangesBanner({lastSyncedAt, onChanged}) {
const {t} = useTranslation();
const [result, setResult] = useState(null);
const [dismissed, setDismissed] = useState(null);
@@ -71,7 +72,7 @@ export default function SyncChangesBanner({lastSyncedAt}) {
- {detailsOpen && setDetailsOpen(false)}/>}
+ {detailsOpen && setDetailsOpen(false)} onChanged={onChanged}/>}
);
}
diff --git a/src/components/SyncChangesDialog.jsx b/src/components/SyncChangesDialog.jsx
index c2a8210..4d0cb61 100644
--- a/src/components/SyncChangesDialog.jsx
+++ b/src/components/SyncChangesDialog.jsx
@@ -3,6 +3,7 @@ import {useTranslation} from 'react-i18next';
import {Dialog, DialogContent, DialogDescription, DialogTitle} from '@/components/ui/dialog';
import {Skeleton} from '@/components/ui/skeleton';
+import SyncCompletedButton from './SyncCompletedButton.jsx';
import GameRow from './stats/GameRow.jsx';
import {formatPlaytime} from '../utils/games.js';
import {fetchSyncChanges, groupByKind} from '../utils/syncChanges.js';
@@ -10,8 +11,10 @@ import {fetchSyncChanges, groupByKind} from '../utils/syncChanges.js';
/**
* The changes of the last sync, grouped by kind. Loads the result again when it opens: the achievements of a sync
* arrive after its games.
+ *
+ * @param onChanged called after a game was set to COMPLETED from here
*/
-export default function SyncChangesDialog({onClose}) {
+export default function SyncChangesDialog({onClose, onChanged}) {
const {t} = useTranslation();
const [changes, setChanges] = useState(null);
const [failed, setFailed] = useState(false);
@@ -52,7 +55,12 @@ export default function SyncChangesDialog({onClose}) {