Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ dist/
# environment variables
.env
dfx.json
.astro
.astro/
5 changes: 0 additions & 5 deletions src/atlas_frontend/.astro/settings.json

This file was deleted.

1 change: 0 additions & 1 deletion src/atlas_frontend/.astro/types.d.ts

This file was deleted.

16 changes: 15 additions & 1 deletion src/atlas_frontend/src/canisters/atlasMain/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,21 @@ import type { Dispatch } from "react";
import type { UnknownAction } from "@reduxjs/toolkit";
import { setUserBlockchainData } from "../../store/slices/userSlice.js";
import { unwrapCall } from "../delegatedCall.js";
import { setConfig } from "../../store/slices/appSlice.js";
import { setConfig, setLastFetchTime } from "../../store/slices/appSlice.js";
import { setSpaces } from "../../store/slices/spacesSlice.js";
import type { ExternalLinks } from "../atlasSpace/types.js";

const FETCH_INTERVAL_MINUTES = parseInt(process.env.FETCH_INTERVAL_MINUTES!)

const shouldFetchSpaces = (lastFetchTime: string | null): boolean => {
if (!lastFetchTime) return true;
const lastFetch = new Date(lastFetchTime);
const now = new Date();
const diffMinutes = (now.getTime() - lastFetch.getTime()) / (1000 * 60);
return diffMinutes >= FETCH_INTERVAL_MINUTES;
};

export { shouldFetchSpaces };
interface CreateNewSpaceArgs {
authAtlasMain: ActorSubclass<_SERVICE_MAIN>;
name: string;
Expand Down Expand Up @@ -84,6 +95,9 @@ export const getAllSpaces = async ({
unAuthAtlasMain,
dispatch,
}: GetAtlasData) => {
const now = new Date().toISOString();
dispatch(setLastFetchTime(now));

let spacesCount = 0n;
let start = 0n;
const count = 200n;
Expand Down
36 changes: 19 additions & 17 deletions src/atlas_frontend/src/canisters/atlasSpace/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,24 +48,26 @@ export const getAtlasSpace = async ({
}
const externalLinksObj = Object.fromEntries(state.external_links);

dispatch(
setSpace({
spaceId,
state: {
...state,
version,
space_symbol: state.space_symbol.pop() ?? null,
space_background: state.space_background.pop() ?? null,
space_logo: state.space_logo.pop() ?? null,
external_links: {
x: externalLinksObj?.x ?? null,
telegram: externalLinksObj?.telegram ?? null,
discord: externalLinksObj?.discord ?? null,
linkedIn: externalLinksObj?.linkedIn ?? null,
},
const spaceData = {
spaceId,
state: {
...state,
version,
space_symbol: state.space_symbol.pop() ?? null,
space_background: state.space_background.pop() ?? null,
space_logo: state.space_logo.pop() ?? null,
external_links: {
x: externalLinksObj?.x ?? null,
telegram: externalLinksObj?.telegram ?? null,
discord: externalLinksObj?.discord ?? null,
linkedIn: externalLinksObj?.linkedIn ?? null,
},
})
);
},
};

dispatch(setSpace(spaceData));

return spaceData;
};

interface CreateNewSpaceTaskArgs {
Expand Down
155 changes: 105 additions & 50 deletions src/atlas_frontend/src/components/Space/SpacesList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,69 +1,124 @@
import React, { useEffect, useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
getUnAuthAtlasSpaceActor,
useUnAuthAgent,
useUnAuthAtlasMainActor,
} from "../../../hooks/identityKit";
import { getAllSpaces } from "../../../canisters/atlasMain/api";
import { deserialize, type RootState } from "../../../store/store";
import { deserialize, type AppDispatch, type RootState } from "../../../store/store";
import { Principal } from "@dfinity/principal";
import { getAtlasSpace } from "../../../canisters/atlasSpace/api";
import SpaceItem from "./SpaceItem";
import { useNavigate } from "react-router-dom";
import type { Spaces } from "../../../store/slices/spacesSlice";
import LocalBlurOverlay from "../../Shared/LocalBlurOverlay";
import {
loadSpacesFromLocalStorage,
saveSpacesToLocalStorage,
} from "../../../store/slices/spacesSlice";

const withLoading = async (fn: () => Promise<void>, set: (b: boolean) => void) => {
set(true);
try {
await fn();
} finally {
set(false);
}
};

const SpacesList = () => {
const dispatch = useDispatch();
const dispatch = useDispatch<AppDispatch>();
const unAuthAtlasMain = useUnAuthAtlasMainActor();
const spaces = deserialize<Spaces>(useSelector((state: RootState) => state.spaces.spaces));
const [fetchedSpacesData, setFetchedSpacesData] = useState(false);
const [fetchingInProgress, setFetchingInProgress] = useState(true);
const agent = useUnAuthAgent();
const navigate = useNavigate();

const spaces = deserialize<Spaces>(
useSelector((state: RootState) => state.spaces.spaces)
);

const actorsReady = Boolean(unAuthAtlasMain && agent);
const hasData = Object.keys(spaces ?? {}).length > 0;

const [isLoading, setIsLoading] = useState<boolean>(() => {
if (hasData) return false;
try {
return localStorage.getItem("atlasSpaces") == null;
} catch {
return true;
}
});

const bootedRef = useRef(false);
const fetchedOnceRef = useRef(false);

const fetchFromApi = async () => {
if (!actorsReady) return;
try {
const spacesIDs = await getAllSpaces({ dispatch, unAuthAtlasMain: unAuthAtlasMain! });
const ids = Object.keys(spacesIDs);

await Promise.all(
ids.map(async (spaceId) => {
const principal = Principal.from(spaceId);
const actor = getUnAuthAtlasSpaceActor(agent!, principal);
if (!actor) return;
await getAtlasSpace({ spaceId, unAuthAtlasSpace: actor, dispatch });
})
);
} catch(error){
console.error("Failed to load spaces data from localStorage:", error); }
};

useEffect(() => {
const fetchSpaces = async () => {
if (unAuthAtlasMain && !spaces) {
await getAllSpaces({
dispatch,
unAuthAtlasMain,
});
setFetchingInProgress(false);
(async () => {
if (!bootedRef.current) {
bootedRef.current = true;

let ls = { hasEntry: false, hasData: false };
if (!hasData) {
ls = await dispatch(loadSpacesFromLocalStorage());
}

const showNow = hasData || ls.hasData || (ls.hasEntry && !ls.hasData);
const fetchNow = !showNow && actorsReady;

setIsLoading(!showNow);

if (fetchNow) {
fetchedOnceRef.current = true;
await withLoading(fetchFromApi, setIsLoading);
return;
}
}

if (actorsReady && !fetchedOnceRef.current) {
fetchedOnceRef.current = true;

const hasLS = (() => {
try {
return localStorage.getItem("atlasSpaces") !== null;
} catch {
return false;
}
})();

const block = !hasData && !hasLS;
if (block) await withLoading(fetchFromApi, setIsLoading);
else await fetchFromApi();
}
};
fetchSpaces();
}, [dispatch, unAuthAtlasMain]);
})();
}, [actorsReady]);

useEffect(() => {
if (!spaces || fetchedSpacesData || !agent) return;
Object.keys(spaces).map(async (spaceId) => {
const spacePrincipal = Principal.from(spaceId);
getUnAuthAtlasSpaceActor(agent, spacePrincipal);
const unAuthAtlasSpace = getUnAuthAtlasSpaceActor(agent, spacePrincipal);
if (!unAuthAtlasSpace) return;
await getAtlasSpace({
spaceId,
unAuthAtlasSpace,
dispatch,
});
});
setFetchedSpacesData(true);
}, [dispatch, spaces, fetchedSpacesData]);

if (!spaces) {
if (!fetchingInProgress) navigate("/");
return (
<LocalBlurOverlay isLoading={true} />
)
}
dispatch(saveSpacesToLocalStorage());
}, [spaces, dispatch]);

if (isLoading) return <LocalBlurOverlay isLoading={true} />;

const spacesEntries = Object.entries(spaces);
if (spacesEntries.length > 0) {
const entries = Object.entries(spaces ?? {});
if (entries.length > 0) {
return (
<div className="grid grid-cols-3 gap-2 container mx-auto my-4">
{spacesEntries.map(
{entries.map(
([key, value]) =>
value?.state && (
<SpaceItem
Expand All @@ -78,15 +133,15 @@ const SpacesList = () => {
)}
</div>
);
} else {
return (
<div className="flex items-center justify-center">
<h1 className="text-white font-montserrat font-medium text-2xl mt-16">
No spaces found
</h1>
</div>
);
}

return (
<div className="flex items-center justify-center">
<h1 className="text-white font-montserrat font-medium text-2xl mt-16">
No spaces found
</h1>
</div>
);
};

export default SpacesList;
7 changes: 6 additions & 1 deletion src/atlas_frontend/src/store/slices/appSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ interface AppState {
blockchainConfig: null | StorableConfig;
isScreenBlur: boolean;
isLoading: boolean;
lastFetchTime: string | null;
}

const initialState: AppState = {
blockchainConfig: null,
isScreenBlur: false,
isLoading: false,
lastFetchTime: null,
};

export const appSlice = createSlice({
Expand All @@ -31,6 +33,9 @@ export const appSlice = createSlice({
setConfig: (state, action: PayloadAction<StorableConfig>) => {
state.blockchainConfig = action.payload;
},
setLastFetchTime: (state, action: PayloadAction<string>) => {
state.lastFetchTime = action.payload;
},
},
selectors: {
selectBlockchainConfig: (state: AppState) => {
Expand All @@ -40,7 +45,7 @@ export const appSlice = createSlice({
}
});

export const { setScreenBlur, setConfig, setLoading } =
export const { setScreenBlur, setConfig, setLoading, setLastFetchTime } =
appSlice.actions;
export const { selectBlockchainConfig } = appSlice.selectors;

Expand Down
Loading