feat: Add planets API and translations (safe incremental)

- Add planet API functions to client.js (fetchPlanets, createPlanet, etc.)
- Add updateIsland function for island customization
- Add sociowire:create-post event listener in App.jsx
- Add planets translations (EN, FR, ES)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-01-24 07:34:45 +00:00
parent c50c61ff36
commit 733296dd59
5 changed files with 219 additions and 3 deletions

View File

@ -147,6 +147,18 @@ export default function App() {
return () => window.removeEventListener("sw:openPostDetail", handleOpenPostDetail);
}, []);
// Listen for island post creation event
useEffect(() => {
const handleIslandPost = (e) => {
const { island } = e.detail || {};
if (island) {
window.location.href = `/island/${island}?create=true`;
}
};
window.addEventListener('sociowire:create-post', handleIslandPost);
return () => window.removeEventListener('sociowire:create-post', handleIslandPost);
}, []);
// Island Universe handlers
const handleOpenUniverse = () => {
setShowUniverse(true);

View File

@ -1250,3 +1250,192 @@ export function getCurrentLocation() {
);
});
}
// ============================================
// ISLAND CUSTOMIZATION API
// ============================================
/**
* Update island customization (terrain, colors, size)
*/
export async function updateIsland(islandId, updates) {
if (!islandId) return { ok: false, error: "Missing island ID" };
try {
const res = await fetch(`${apiBase()}/islands/${encodeURIComponent(islandId)}`, {
method: "PATCH",
credentials: "include",
headers: { "Content-Type": "application/json", ...authHeaders() },
body: JSON.stringify(updates),
});
const data = await res.json().catch(() => ({}));
return { ok: res.ok, ...data };
} catch (err) {
console.warn("updateIsland error:", err);
return { ok: false, error: err?.message || "Update failed" };
}
}
// ============================================
// PLANETS API (Group spaces)
// ============================================
/**
* Fetch list of planets (group spaces)
*/
export async function fetchPlanets(params = {}) {
const qs = new URLSearchParams();
if (typeof params.limit === "number") qs.set("limit", String(params.limit));
if (typeof params.offset === "number") qs.set("offset", String(params.offset));
if (params.search) qs.set("search", params.search);
const url = `${apiBase()}/planets?${qs.toString()}`;
try {
const res = await fetch(url, { credentials: "include" });
if (!res.ok) {
console.warn("fetchPlanets failed", res.status);
return [];
}
const data = await res.json();
return Array.isArray(data) ? data : data.planets || [];
} catch (err) {
console.warn("fetchPlanets error:", err);
return [];
}
}
/**
* Fetch single planet by ID
*/
export async function fetchPlanet(planetId) {
if (!planetId) return null;
const url = `${apiBase()}/planets/${encodeURIComponent(planetId)}`;
try {
const res = await fetch(url, { credentials: "include" });
if (!res.ok) {
console.warn("fetchPlanet failed", res.status);
return null;
}
return await res.json();
} catch (err) {
console.warn("fetchPlanet error:", err);
return null;
}
}
/**
* Create a new planet
*/
export async function createPlanet(payload) {
try {
const res = await fetch(`${apiBase()}/planets`, {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json", ...authHeaders() },
body: JSON.stringify(payload),
});
const data = await res.json().catch(() => ({}));
return { ok: res.ok, ...data };
} catch (err) {
console.warn("createPlanet error:", err);
return { ok: false, error: err?.message || "Create failed" };
}
}
/**
* Update planet settings
*/
export async function updatePlanet(planetId, updates) {
if (!planetId) return { ok: false, error: "Missing planet ID" };
try {
const res = await fetch(`${apiBase()}/planets/${encodeURIComponent(planetId)}`, {
method: "PATCH",
credentials: "include",
headers: { "Content-Type": "application/json", ...authHeaders() },
body: JSON.stringify(updates),
});
const data = await res.json().catch(() => ({}));
return { ok: res.ok, ...data };
} catch (err) {
console.warn("updatePlanet error:", err);
return { ok: false, error: err?.message || "Update failed" };
}
}
/**
* Join a planet
*/
export async function joinPlanet(planetId) {
if (!planetId) return { ok: false };
try {
const res = await fetch(`${apiBase()}/planets/${encodeURIComponent(planetId)}/join`, {
method: "POST",
credentials: "include",
headers: { ...authHeaders() },
});
const data = await res.json().catch(() => ({}));
return { ok: res.ok, ...data };
} catch (err) {
console.warn("joinPlanet error:", err);
return { ok: false };
}
}
/**
* Leave a planet
*/
export async function leavePlanet(planetId) {
if (!planetId) return { ok: false };
try {
const res = await fetch(`${apiBase()}/planets/${encodeURIComponent(planetId)}/leave`, {
method: "POST",
credentials: "include",
headers: { ...authHeaders() },
});
const data = await res.json().catch(() => ({}));
return { ok: res.ok, ...data };
} catch (err) {
console.warn("leavePlanet error:", err);
return { ok: false };
}
}
/**
* Fetch planet members
*/
export async function fetchPlanetMembers(planetId, params = {}) {
if (!planetId) return [];
const qs = new URLSearchParams();
if (typeof params.limit === "number") qs.set("limit", String(params.limit));
try {
const res = await fetch(`${apiBase()}/planets/${encodeURIComponent(planetId)}/members?${qs.toString()}`, {
credentials: "include",
});
if (!res.ok) return [];
const data = await res.json();
return Array.isArray(data) ? data : data.members || [];
} catch (err) {
console.warn("fetchPlanetMembers error:", err);
return [];
}
}
/**
* Fetch user's planets (planets they've joined)
*/
export async function fetchUserPlanets() {
try {
const res = await fetch(`${apiBase()}/planets/my`, {
credentials: "include",
headers: { ...authHeaders() },
});
if (!res.ok) return [];
const data = await res.json();
return Array.isArray(data) ? data : data.planets || [];
} catch (err) {
console.warn("fetchUserPlanets error:", err);
return [];
}
}

View File

@ -270,7 +270,12 @@
"islandsCount": "{{count}} islands floating in virtual space",
"clickToVisit": "Click to visit",
"posts": "posts",
"help": "Hover over islands to see details - Click to visit"
"help": "Hover over islands to see details - Click to visit",
"planets": "planets",
"all": "All",
"islandsTab": "Islands",
"planetsTab": "Planets",
"members": "members"
},
"controls": {
"mySpot": "My spot",

View File

@ -270,7 +270,12 @@
"islandsCount": "{{count}} islas flotando en el espacio virtual",
"clickToVisit": "Clic para visitar",
"posts": "posts",
"help": "Pasa el ratón sobre las islas para ver detalles - Clic para visitar"
"help": "Pasa el ratón sobre las islas para ver detalles - Clic para visitar",
"planets": "planetas",
"all": "Todo",
"islandsTab": "Islas",
"planetsTab": "Planetas",
"members": "miembros"
},
"controls": {
"mySpot": "Mi ubicación",

View File

@ -273,7 +273,12 @@
"islandsCount": "{{count}} îles flottant dans l'espace virtuel",
"clickToVisit": "Cliquez pour visiter",
"posts": "posts",
"help": "Survolez les îles pour voir les détails - Cliquez pour visiter"
"help": "Survolez les îles pour voir les détails - Cliquez pour visiter",
"planets": "planètes",
"all": "Tout",
"islandsTab": "Îles",
"planetsTab": "Planètes",
"members": "membres"
},
"controls": {
"mySpot": "Ma position",