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
7 changes: 4 additions & 3 deletions groups/createElements.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const createCard = (
) => {
const group = {
...rawGroup,
count: rawGroup.count || 0,
description:
rawGroup.description ||
'Please ask the role creator or admin to update this group description.',
Expand All @@ -24,7 +25,7 @@ const createCard = (
<img class="delete-group__icon" src="assets/delete.svg" alt="Delete" />
</button>`
: ''
}
}
</div>
<p class="card__description"></p>
<div class="card__action">
Expand Down Expand Up @@ -169,7 +170,7 @@ const createGroupCreationModal = (onClose = () => {}, onSubmit = () => {}) => {
<textarea
class="input__field"
name="description"
placeholder="This group is for..."
placeholder="This group is for..."
></textarea>
</div>
</div>
Expand Down Expand Up @@ -257,7 +258,7 @@ const createDeleteConfirmationModal = (
<div class="delete-modal__content">
<p class="delete-modal__msg"> Are you sure you want to delete this group? </p>
</div>

<div class="delete-modal__buttons">
<button class="delete-modal-button button--secondary" id="cancel-delete">Cancel</button>
<button class="delete-modal-button button--danger" id="confirm-delete">Delete</button>
Expand Down
3 changes: 2 additions & 1 deletion groups/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<script src="/constants.js"></script>
<script src="../utils.js" defer></script>
<script type="module" src="/groups/script.js" defer></script>
<script type="module" src="/groups/lazyLoading.js" defer></script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
Expand All @@ -34,7 +35,7 @@
<section class="group-header">
<div class="search">
<img class="search__icon" src="assets/search.svg" alt="search" />
<input class="search__input" placeholder="Serach for group" />
<input class="search__input" placeholder="Search for group" />
</div>
<div class="create-group">
<button class="button">
Expand Down
96 changes: 96 additions & 0 deletions groups/lazyLoading.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { getDiscordGroupsPaginated } from './utils.js';
import {
renderLoader,
removeLoader,
renderMoreGroups,
renderLoadingCards,
removeLoadingCards,
} from './render.js';
import { getParamValueFromURL } from './utils.js';
import { groupCardOnAction, dataStore } from './script.js';

const isDevMode = getParamValueFromURL('dev') === 'true';

let currentPage = null;
let isFetching = false;
let hasNextPage = true;

if (isDevMode) {
document.addEventListener('DOMContentLoaded', () => {
bindLazyLoading();
loadMoreGroups();
});
}

async function loadMoreGroups() {
if (isFetching || !hasNextPage) return;
isFetching = true;

renderLoadingCards(); // ✅ Show shimmer instead of white screen

try {
const { groups: rawGroups, nextPageUrl } =
currentPage === null
? await getDiscordGroupsPaginated()
: await getDiscordGroupsPaginated(currentPage, 10);

if (!rawGroups || rawGroups.length === 0) {
hasNextPage = false;
removeLoader();
return;
}

const formattedGroups = rawGroups.map((group) => ({
...group,
title: group.rolename
.replace('group-', '')
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' '),
}));

if (!dataStore.groups) {
dataStore.groups = {};
}

formattedGroups.forEach((group) => {
dataStore.groups[group.id] = {
...group,
count: group.memberCount,
isMember: group.isMember,
isUpdating: false,
};
});

dataStore.filteredGroupsIds = Object.keys(dataStore.groups);

renderMoreGroups({
groups: formattedGroups,
cardOnClick: groupCardOnAction,
});

currentPage = nextPageUrl
? currentPage === null
? 1
: currentPage + 1
: null;
hasNextPage = !!nextPageUrl;
} catch (error) {
console.error('Error fetching more groups:', error);
} finally {
removeLoadingCards();
removeLoader();
isFetching = false;
}
}

function bindLazyLoading() {
window.addEventListener('scroll', () => {
if (
window.innerHeight + window.scrollY >= document.body.offsetHeight - 500 &&
!isFetching
) {
loadMoreGroups();
}
});
}
22 changes: 22 additions & 0 deletions groups/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,27 @@ const renderGroupById = ({
}
};

const renderMoreGroups = ({
groups,
cardOnClick = () => {},
isSuperUser = false,
}) => {
const mainContainer = document.querySelector('.group-container');
console.log('Rendering groups:', groups);

groups.forEach((group) => {
if (!document.getElementById(`group-${group.id}`)) {
const card = createCard(
group,
() => cardOnClick(group.id),
() => {},
isSuperUser,
);
mainContainer.appendChild(card);
}
});
};

const renderNoGroupFound = () => {
const mainContainer = document.querySelector('.group-container');
const noGroupContainer = document.createElement('div');
Expand Down Expand Up @@ -166,6 +187,7 @@ export {
renderLoadingCards,
removeLoadingCards,
renderGroupById,
renderMoreGroups,
renderNoGroupFound,
renderDeleteConfirmationModal,
removeDeleteConfirmationModal,
Expand Down
19 changes: 15 additions & 4 deletions groups/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
setParamValueInURL,
deleteDiscordGroupRole,
} from './utils.js';
const isDevMode = getParamValueFromURL('dev') === 'true';

const QUERY_PARAM_KEY = {
DEV_FEATURE_FLAG: 'dev',
Expand Down Expand Up @@ -169,10 +170,15 @@ const onCreate = () => {
bindSearchFocus();
bindGroupCreationButton();
};

const afterAuthentication = async () => {
renderNavbarProfile({ profile: dataStore.userSelf });
dataStore.isSuperUser = await checkUserIsSuperUser();

if (isDevMode) {
return;
}

await Promise.all([getDiscordGroups(), getUserGroupRoles()]).then(
([groups, roleData]) => {
const nonDeletedGroups = groups.filter((group) => !group.isDeleted);
Expand All @@ -199,9 +205,7 @@ const afterAuthentication = async () => {
dataStore.search,
);
dataStore.discordId = roleData.userId;
renderAllGroups({
cardOnClick: groupCardOnAction,
});
renderAllGroups({ cardOnClick: groupCardOnAction });
},
);
};
Expand Down Expand Up @@ -254,9 +258,14 @@ function updateGroup(id, group) {
};
}

function groupCardOnAction(id) {
export function groupCardOnAction(id) {
if (!dataStore.groups) return;

const group = dataStore.groups[id];
if (!group) return;

updateGroup(id, { isUpdating: true });

if (group.isMember) {
removeRoleFromMember(group.roleId, dataStore.discordId)
.then(() => updateGroup(id, { isMember: false, count: group.count - 1 }))
Expand Down Expand Up @@ -326,3 +335,5 @@ function showDeleteModal(groupId) {
}

onCreate();

export { dataStore };
28 changes: 28 additions & 0 deletions groups/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,33 @@ async function getDiscordGroups() {
return err;
}
}

async function getDiscordGroupsPaginated(page = null, size = 10) {
try {
const url =
page === null
? `${BASE_URL}/discord-actions/groups?dev=true`
: `${BASE_URL}/discord-actions/groups?page=${page}&size=${size}&dev=true`;

const res = await fetch(url, {
method: 'GET',
credentials: 'include',
headers: { 'Content-type': 'application/json' },
});

if (!res.ok) {
const error = await res.json();
throw new Error(error.message || 'Failed to fetch groups');
}

const { groups, links } = await res.json();
return { groups, nextPageUrl: links?.next || null };
} catch (err) {
console.error('Error fetching paginated groups:', err);
throw err;
}
}

async function createDiscordGroupRole(groupRoleBody) {
try {
const res = await fetch(`${BASE_URL}/discord-actions/groups`, {
Expand Down Expand Up @@ -183,6 +210,7 @@ export {
getMembers,
getUserSelf,
getDiscordGroups,
getDiscordGroupsPaginated,
createDiscordGroupRole,
addGroupRoleToMember,
removeRoleFromMember,
Expand Down
Loading