diff --git a/src/api/client.js b/src/api/client.js
index 0b00a09..13b677f 100644
--- a/src/api/client.js
+++ b/src/api/client.js
@@ -296,109 +296,6 @@ export async function pollNewPosts(params = {}) {
* Replace with real API call when island-service is ready
*/
export async function fetchIslands(params = {}) {
- // MOCK DATA - Remove this and uncomment API call when backend is ready
- console.log('[fetchIslands] Using MOCK data', params);
-
- // Simulate network delay
- await new Promise(resolve => setTimeout(resolve, 300));
-
- const mockIslands = [
- {
- id: 'island-1',
- user_id: 'user-alice',
- username: 'alice',
- seed: 12345678,
- virtual_x: -73.5,
- virtual_y: 45.5,
- virtual_z: 0,
- terrain_type: 'tropical',
- color_palette: 'vibrant',
- size: 'medium',
- display_name: "Alice's Paradise",
- bio: 'Welcome to my tropical island! 🌴',
- avatar_url: null,
- content_count: 12,
- visitors: 234,
- created_at: '2025-01-15T10:00:00Z'
- },
- {
- id: 'island-2',
- user_id: 'user-bob',
- username: 'bob',
- seed: 87654321,
- virtual_x: -74.0,
- virtual_y: 45.8,
- virtual_z: 0,
- terrain_type: 'desert',
- color_palette: 'sunset',
- size: 'large',
- display_name: "Bob's Oasis",
- bio: 'Desert vibes only ☀️',
- avatar_url: null,
- content_count: 8,
- visitors: 156,
- created_at: '2025-01-10T14:30:00Z'
- },
- {
- id: 'island-3',
- user_id: 'user-charlie',
- username: 'charlie',
- seed: 11223344,
- virtual_x: -73.2,
- virtual_y: 45.3,
- virtual_z: 0,
- terrain_type: 'arctic',
- color_palette: 'monochrome',
- size: 'small',
- display_name: "Charlie's Iceberg",
- bio: 'Chill zone ❄️',
- avatar_url: null,
- content_count: 5,
- visitors: 89,
- created_at: '2025-01-20T09:15:00Z'
- },
- {
- id: 'island-4',
- user_id: 'user-diana',
- username: 'diana',
- seed: 99887766,
- virtual_x: -73.8,
- virtual_y: 45.6,
- virtual_z: 0,
- terrain_type: 'volcanic',
- color_palette: 'vibrant',
- size: 'medium',
- display_name: "Diana's Volcano",
- bio: 'Hot takes and lava cakes 🌋',
- avatar_url: null,
- content_count: 15,
- visitors: 312,
- created_at: '2025-01-12T16:45:00Z'
- },
- {
- id: 'island-5',
- user_id: 'user-eve',
- username: 'eve',
- seed: 55443322,
- virtual_x: -74.2,
- virtual_y: 45.4,
- virtual_z: 0,
- terrain_type: 'meadow',
- color_palette: 'pastel',
- size: 'large',
- display_name: "Eve's Garden",
- bio: 'Peace, love, and flowers 🌸',
- avatar_url: null,
- content_count: 20,
- visitors: 445,
- created_at: '2025-01-08T11:20:00Z'
- }
- ];
-
- return mockIslands;
-
- /*
- // REAL API CALL - Uncomment when island-service is ready:
const qs = new URLSearchParams();
if (typeof params.lat === "number") qs.set("lat", String(params.lat));
@@ -406,7 +303,7 @@ export async function fetchIslands(params = {}) {
if (typeof params.radius_km === "number") qs.set("radius_km", String(params.radius_km));
if (typeof params.limit === "number") qs.set("limit", String(params.limit));
- const url = `http://localhost:8090/api/islands/nearby?${qs.toString()}`;
+ const url = `${API_BASE}/islands/nearby?${qs.toString()}`;
try {
const res = await fetch(url, { credentials: "include" });
@@ -420,7 +317,6 @@ export async function fetchIslands(params = {}) {
console.warn("fetchIslands error:", err);
return [];
}
- */
}
/**
@@ -430,108 +326,8 @@ export async function fetchIslands(params = {}) {
* NOTE: Currently using MOCK DATA for testing
*/
export async function fetchIsland(islandId) {
- // MOCK DATA - Remove this and uncomment API call when backend is ready
- console.log('[fetchIsland] Using MOCK data for island:', islandId);
-
- // Simulate network delay
- await new Promise(resolve => setTimeout(resolve, 200));
-
- // Mock island with content
- const mockIslandData = {
- 'island-1': {
- id: 'island-1',
- user_id: 'user-alice',
- username: 'alice',
- seed: 12345678,
- virtual_x: -73.5,
- virtual_y: 45.5,
- virtual_z: 0,
- terrain_type: 'tropical',
- color_palette: 'vibrant',
- size: 'medium',
- display_name: "Alice's Paradise",
- bio: 'Welcome to my tropical island! 🌴',
- avatar_url: null,
- content_count: 3,
- visitors: 234,
- created_at: '2025-01-15T10:00:00Z',
- content: [
- {
- id: 'content-1',
- island_id: 'island-1',
- content_type: 'post',
- content_id: 'post-123',
- position_x: 0.3,
- position_y: 0.7,
- position_z: 0.5,
- scale: 1.0,
- rotation: 0,
- created_at: '2025-01-16T10:00:00Z'
- },
- {
- id: 'content-2',
- island_id: 'island-1',
- content_type: 'app',
- content_id: 'app-456',
- position_x: 0.6,
- position_y: 0.4,
- position_z: 0.5,
- scale: 1.0,
- rotation: 45,
- created_at: '2025-01-17T14:30:00Z'
- },
- {
- id: 'content-3',
- island_id: 'island-1',
- content_type: 'post',
- content_id: 'post-789',
- position_x: 0.8,
- position_y: 0.8,
- position_z: 0.5,
- scale: 1.0,
- rotation: 90,
- created_at: '2025-01-18T09:15:00Z'
- }
- ]
- },
- 'island-2': {
- id: 'island-2',
- user_id: 'user-bob',
- username: 'bob',
- seed: 87654321,
- virtual_x: -74.0,
- virtual_y: 45.8,
- terrain_type: 'desert',
- color_palette: 'sunset',
- size: 'large',
- display_name: "Bob's Oasis",
- bio: 'Desert vibes only ☀️',
- content_count: 2,
- visitors: 156,
- content: [
- {
- id: 'content-4',
- content_type: 'post',
- position_x: 0.5,
- position_y: 0.5,
- position_z: 0.5
- },
- {
- id: 'content-5',
- content_type: 'document',
- position_x: 0.2,
- position_y: 0.3,
- position_z: 0.5
- }
- ]
- }
- };
-
- return mockIslandData[islandId] || null;
-
- /*
- // REAL API CALL - Uncomment when island-service is ready:
- const url = `http://localhost:8090/api/islands/${islandId}`;
+ if (!islandId) return null;
+ const url = `${API_BASE}/islands/${encodeURIComponent(islandId)}`;
try {
const res = await fetch(url, { credentials: "include" });
@@ -544,7 +340,45 @@ export async function fetchIsland(islandId) {
console.warn("fetchIsland error:", err);
return null;
}
- */
+}
+
+export async function fetchIslandByUsername(username) {
+ if (!username) return null;
+ const url = `${API_BASE}/islands/by-username?username=${encodeURIComponent(username)}`;
+ try {
+ const res = await fetch(url, { credentials: "include" });
+ if (!res.ok) {
+ console.warn("fetchIslandByUsername failed", res.status);
+ return null;
+ }
+ return await res.json();
+ } catch (err) {
+ console.warn("fetchIslandByUsername error:", err);
+ return null;
+ }
+}
+
+export async function uploadProfileAvatar(file) {
+ if (!file) return { ok: false };
+ const url = `${API_BASE}/profile/avatar`;
+ const form = new FormData();
+ form.append("file", file);
+ try {
+ const res = await fetch(url, {
+ method: "POST",
+ body: form,
+ credentials: "include",
+ headers: { ...authHeaders() },
+ });
+ if (!res.ok) {
+ const text = await res.text().catch(() => "");
+ return { ok: false, status: res.status, text };
+ }
+ return await res.json();
+ } catch (err) {
+ console.warn("uploadProfileAvatar error:", err);
+ return { ok: false };
+ }
}
export async function recordPostView(postId) {
diff --git a/src/auth/AuthContext.jsx b/src/auth/AuthContext.jsx
index ede193c..dd4877c 100644
--- a/src/auth/AuthContext.jsx
+++ b/src/auth/AuthContext.jsx
@@ -132,6 +132,7 @@ async function me(accessToken) {
json,
text,
emailVerified,
+ avatarUrl: u && typeof u.avatar_url === "string" ? u.avatar_url : "",
};
}
@@ -182,6 +183,7 @@ export function AuthProvider({ children }) {
const [status, setStatus] = useState("loading"); // loading | anon | auth
const [user, setUser] = useState(null); // { username }
const [tokens, setTokens] = useState(null); // { access_token, refresh_token, ... }
+ const [avatarUrl, setAvatarUrl] = useState("");
const [error, setError] = useState("");
const [lastInfo, setLastInfo] = useState("");
@@ -194,6 +196,7 @@ export function AuthProvider({ children }) {
setStatus("anon");
setUser(null);
setTokens(null);
+ setAvatarUrl("");
setError(msg || "");
setLastInfo("");
setNeedsEmailVerification(false);
@@ -218,6 +221,9 @@ export function AuthProvider({ children }) {
// /api/me is the truth; token claim is fallback only
const mr = await me(at);
let verified = mr.emailVerified
+ if (mr.avatarUrl) {
+ setAvatarUrl(mr.avatarUrl);
+ }
if (typeof verified !== "boolean") {
const tv = tokenEmailVerified(at);
@@ -553,6 +559,7 @@ export function AuthProvider({ children }) {
user,
tokens,
error,
+ avatarUrl,
// expected by UI
loading,
@@ -571,13 +578,14 @@ export function AuthProvider({ children }) {
logout,
restore,
refreshProfile,
+ setAvatarUrl,
setError,
setLastInfo,
setNeedsEmailVerification,
clearError: () => setError(""),
clearInfo: () => setLastInfo(""),
};
- }, [status, user, tokens, error, lastInfo, needsEmailVerification]);
+ }, [status, user, tokens, error, lastInfo, needsEmailVerification, avatarUrl]);
return
{island.display_name}
)} + {isSelf ? ( +