Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2f513d3
Merge pull request #291 from sparrowapp-dev/development
Astitva877 Sep 11, 2025
61939e9
Merge pull request #293 from sparrowapp-dev/development
Astitva877 Sep 24, 2025
8e64bc9
Merge pull request #296 from sparrowapp-dev/development
Astitva877 Oct 3, 2025
c8ec325
feat:downgrade feature for users to downgrade from higher to lower pl…
rudransh2003 Oct 10, 2025
6e6948f
feat:downgrade modals for downgrade from higher to lower plans[2164]
rudransh2003 Oct 13, 2025
de4091f
feat: Selection of members as per selected workspaces when downgradin…
rudransh2003 Oct 14, 2025
77fd2e5
feat: version upgrade to 2.33.0
itsmdasifraza Oct 15, 2025
8d06656
Merge pull request #298 from sparrowapp-dev/feat/version-upgrade-to-2…
itsmdasifraza Oct 15, 2025
fbca7b5
Manual downgrade functionality for community and standard plans corre…
rudransh2003 Oct 17, 2025
04a701a
feat:downgrade for community and standard plans[2545]
rudransh2003 Oct 17, 2025
13eb2ad
remove console logs[2545]
rudransh2003 Oct 17, 2025
48ab2cd
updated cancelSubscription method and removd data param from delete m…
rudransh2003 Oct 17, 2025
ef61dbe
Merge pull request #297 from sparrowapp-dev/feat/2164/maually-downgra…
itsmdasifraza Oct 22, 2025
64bdafb
fix: updated payload property variable
aakashreddy-p Oct 22, 2025
7a78994
Merge pull request #300 from sparrowapp-dev/aakash/fix/cancel-subscri…
itsmdasifraza Oct 22, 2025
f887c8f
fix: updated subscription meta payload
aakashreddy-p Oct 22, 2025
1d8bc18
Merge pull request #301 from sparrowapp-dev/aakash/fix/payload-issue-…
itsmdasifraza Oct 22, 2025
986dd65
fixed ui issues on the manual downgrade[2164/2545]
rudransh2003 Oct 23, 2025
cf021fc
removed logs
rudransh2003 Oct 23, 2025
90085fc
adding support mail and fix hubOwner filtering logic[2164/2545]
rudransh2003 Oct 23, 2025
7c1c0a1
Merge pull request #302 from sparrowapp-dev/feat/2164/maually-downgra…
itsmdasifraza Oct 23, 2025
c82571d
Made lower plans unselectable when in a scheduled downgrade and chang…
rudransh2003 Oct 27, 2025
38cce03
complete downgrade flow for cancel subscription button on overview page
rudransh2003 Oct 28, 2025
9b1d86b
update version to 2.34.0
rudransh2003 Oct 28, 2025
1ee9552
Merge pull request #303 from sparrowapp-dev/feat/2164/maually-downgra…
itsmdasifraza Oct 28, 2025
a39e8f1
fix: update branch name for staging workflow trigger
Oct 29, 2025
0e442d9
Merge pull request #307 from sparrowapp-dev/dev/aarti
jatinlodhi2002 Oct 29, 2025
42a37f2
fix: fixed processing button when activeworkspace modal is closed
rudransh2003 Oct 29, 2025
b571331
Merge pull request #309 from sparrowapp-dev/bug/fix-processing-when-w…
aakashreddy-p Oct 30, 2025
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 .github/workflows/sparrowadmin-staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Sparrow-admin-Stg
on:
push:
branches:
- release/stg-workflow
- release/2.34.0
workflow_dispatch:

jobs:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "sparrow-admin-app",
"private": true,
"version": "2.32.1",
"version": "2.34.0",
"type": "module",
"scripts": {
"dev": "vite --host",
Expand Down
7 changes: 7 additions & 0 deletions src/assets/icons/ErrorCircleIcon.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="32" height="32" rx="16" fill="#31353F" />
<path
d="M16 6C21.523 6 26 10.478 26 16C26 21.522 21.523 26 16 26C10.477 26 6 21.522 6 16C6 10.478 10.477 6 16 6ZM16.0018 19.0037C15.4503 19.0037 15.0031 19.4508 15.0031 20.0024C15.0031 20.5539 15.4503 21.001 16.0018 21.001C16.5533 21.001 17.0005 20.5539 17.0005 20.0024C17.0005 19.4508 16.5533 19.0037 16.0018 19.0037ZM15.9996 11C15.4868 11.0002 15.0643 11.3864 15.0067 11.8837L15 12.0004L15.0018 17.0012L15.0086 17.1179C15.0665 17.6152 15.4893 18.0011 16.0022 18.0009C16.515 18.0007 16.9375 17.6145 16.9951 17.1171L17.0018 17.0005L17 11.9996L16.9932 11.883C16.9353 11.3857 16.5125 10.9998 15.9996 11Z"
fill="#EB5651"
/>
</svg>
184 changes: 184 additions & 0 deletions src/components/DowngradePlan/ChooseActiveMembersModal.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
<script lang="ts">
import { createEventDispatcher, onMount } from 'svelte';
import DowngradeModalButtons from '@/ui/DowngradeModalButtons/DowngradeModalButtons.svelte';
import TableModalCommonLayout from './Common/TableModalCommonLayout.svelte';
import { hubsService } from '@/services/hubs.service';
import { userService, UserService } from '@/services/users.service';
import { createQuery } from '@/services/api.common';

export let isOpen: boolean = false;
export let planLimits: [];
export let users = [];
export let hubId: string;
export let hubOwner: any;
export let expiryDate: string;
let maxSelectable = 0

const dispatch = createEventDispatcher();
let selected = new Set();
$: {
if (typeof planLimits === 'number') {
maxSelectable = planLimits;
} else if (planLimits && typeof planLimits === 'object' && planLimits?.usersPerHub) {
maxSelectable = planLimits?.usersPerHub?.value;
} else {
maxSelectable = 0;
}
}
$: filteredUsers = Array.isArray(users) ? users : [];

const toggleMember = (id: string) => {
if (selected.has(id)) {
selected.delete(id);
} else if (selected.size < maxSelectable) {
selected.add(id);
}
selected = new Set(selected);
};

const getLastActive = async () => {
try {
const res = await userService.getUsers();
if (res?.data?.users?.length) {
users = users.map((u) => {
const match = res.data.users.find(
(apiUser) => apiUser.id === u.id || apiUser._id === u._id || apiUser.email === u.email,
);

return {
...u,
lastActiveAt: match?.lastActive || u.lastActiveAt || '',
};
});
}
} catch (err) {
console.error('Error fetching last active users:', err);
}
};

const handleNext = () => {
const selectedMembers = filteredUsers
.filter((ws) => selected.has(ws.id))
.map((ws) => ({
id: ws.id,
email: ws.email,
}));
dispatch('next', { selected: selectedMembers });
};
function getFirstName(fullNameOrEmail: string): string {
if (!fullNameOrEmail) return '';
if (fullNameOrEmail.includes('@')) {
return fullNameOrEmail.split('@')[0];
}
return fullNameOrEmail.split(' ')[0];
}

function getTimeDifference(lastActiveAt: string): string {
if (!lastActiveAt) return 'Never';
const lastActiveDate = new Date(lastActiveAt);
const now = new Date();
const diffInMinutes = Math.floor((now.getTime() - lastActiveDate.getTime()) / (1000 * 60));

if (diffInMinutes < 1) return 'Just now';
if (diffInMinutes < 60) return `${diffInMinutes} minute${diffInMinutes > 1 ? 's' : ''} ago`;

const diffInHours = Math.floor(diffInMinutes / 60);
if (diffInHours < 24) return `${diffInHours} hour${diffInHours > 1 ? 's' : ''} ago`;

const diffInDays = Math.floor(diffInHours / 24);
if (diffInDays === 1) return '1 day ago';
return `${diffInDays} days ago`;
}

function getRoleLabel(role: string): string {
const r = role?.toLowerCase?.() || '';
if (r === 'owner') return 'Owner';
if (r === 'admin') return 'Admin';
return 'Member';
}

$: isConfirmEnabled = (() => {
if (!filteredUsers.length) return true;
if (filteredUsers.length <= maxSelectable) {
return selected.size >= 1; // must select at least one
}

return selected.size === maxSelectable; // must select exactly 4
})();

</script>

<TableModalCommonLayout
{isOpen}
title="Choose Your Active Members"
subheading1="Your new plan includes {maxSelectable} active members (plus you as the Hub Owner). Please select the team members to keep active."
subheading2="Unselected members will be removed from the Hub on {expiryDate}."
{maxSelectable}
stepIndicator={true}
selectedCount={selected.size}
isPrimaryDisabled={!isConfirmEnabled}
confirmText="Confirm Selections"
on:confirm={handleNext}
on:close={() => dispatch('close')}
>
<div slot="stepIndicator" class="flex items-center gap-2">
<span class="text-fs-ds-14 text-gray-400">Step 1</span>
<div class="mx-2 h-px w-12 bg-gray-600"></div>
<div class="flex items-center gap-1">
<div
class="text-fs-ds-12 flex h-5 w-5 items-center justify-center rounded-full bg-[#2B74FF] font-medium"
>
2
</div>
<span class="text-fs-ds-14 font-medium text-gray-300">Step 2</span>
</div>
</div>
<div class="flex-grow overflow-y-auto">
{#if filteredUsers.length > 0}
{#each filteredUsers as user}
<div
class="flex cursor-pointer items-center justify-between px-4 py-3 hover:bg-[#2A2F3A] text-left"
on:click={() => toggleMember(user.id)}
>
<div class="flex items-center gap-3">
<label class="relative flex items-center justify-center">
<input
type="checkbox"
checked={selected.has(user.id)}
on:click|stopPropagation={() => toggleMember(user.id)}
disabled={!selected.has(user.id) && selected.size >= maxSelectable}
class="peer h-4 w-4 cursor-pointer appearance-none rounded-sm border border-[#2A2F3A] bg-[#1E222C] checked:border-[#2B74FF] checked:bg-[#2B74FF] focus:outline-none"
/>
<svg
xmlns="http://www.w3.org/2000/svg"
class="pointer-events-none absolute h-3 w-3 text-black opacity-0 transition-opacity peer-checked:opacity-100"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M16.707 5.293a1 1 0 00-1.414 0L8 12.586 4.707 9.293a1 1 0 00-1.414 1.414l4 4a1 1 0 001.414 0l8-8a1 1 0 000-1.414z"
clip-rule="evenodd"
/>
</svg>
</label>
<div>
<span class="text-[12px] font-medium text-white">
{getFirstName(user.name || user.email)}
</span>
<span class="text-[12px] text-gray-400">({getRoleLabel(user.role)})</span>
<div class="truncate text-[11px] text-gray-500">{user.email}</div>
</div>
</div>
<div class="text-[12px] text-gray-400 text-left">
Last active: {getTimeDifference(user.lastActiveAt)}
</div>
</div>
{/each}
{:else}
<div class="flex items-center justify-center py-8 text-sm text-gray-400 italic">
No active members present for the selected workspaces.
</div>
{/if}
</div>
</TableModalCommonLayout>
Loading
Loading