Skip to content

Commit f230946

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat-csv-export
2 parents e0836fb + 9cbf8d2 commit f230946

30 files changed

Lines changed: 236 additions & 132 deletions

File tree

AGENTS.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,23 @@ src/
101101
5. Before commit: `pnpm run check && pnpm run format && pnpm run lint && pnpm run test && pnpm run build`
102102
6. **Take screenshots**: For any UI changes, capture screenshots and include them in the PR description or comments before finalizing
103103

104+
## Required Pre-Completion Checklist
105+
106+
**CRITICAL**: Before finishing any work or marking a task complete, agents MUST run the following commands in order and ensure all pass:
107+
108+
1. **`pnpm run format`** - Auto-fix all formatting issues
109+
2. **`pnpm run check`** - Verify TypeScript/Svelte types (must show 0 errors, 0 warnings)
110+
3. **`pnpm run lint`** - Check code style (ignore pre-existing issues in files you didn't modify)
111+
4. **`pnpm run test`** - Run all unit tests (all tests must pass)
112+
5. **`pnpm run build`** - Ensure production build succeeds
113+
114+
If any command fails:
115+
116+
- **Format/Lint**: Run `pnpm run format` to auto-fix, then re-check
117+
- **Type errors**: Fix all TypeScript errors in files you modified
118+
- **Test failures**: Fix failing tests or ensure failures are unrelated to your changes
119+
- **Build failures**: Debug and resolve build issues before proceeding
120+
121+
**Never skip these checks** - they are mandatory quality gates before any work is considered complete.
122+
104123
**Trust these instructions** - only search if incomplete/incorrect. See CONTRIBUTING.md for PR conventions. Use `--frozen-lockfile` always. Docker builds: multi-stage, final image is nginx serving static files from `/console` path.

src/lib/commandCenter/commandCenter.svelte

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@
3030
import { getContext, setContext } from 'svelte';
3131
import { get, writable, type Readable } from 'svelte/store';
3232
import { fade } from 'svelte/transition';
33-
import { commandCenterKeyDownHandler, disableCommands, registerCommands } from './commands';
33+
import {
34+
commandCenterKeyDownHandler,
35+
disableCommands,
36+
isTargetInputLike,
37+
registerCommands
38+
} from './commands';
3439
import { RootPanel } from './panels';
3540
import { addSubPanel, clearSubPanels, subPanels } from './subPanels';
3641
import { addNotification } from '$lib/stores/notifications';
@@ -95,13 +100,9 @@
95100
keys = [];
96101
}, 1000);
97102
98-
function isInputEvent(event: KeyboardEvent) {
99-
return ['INPUT', 'TEXTAREA', 'SELECT'].includes((event.target as HTMLElement).tagName);
100-
}
101-
102103
const handleKeydown = (e: KeyboardEvent) => {
103104
if (!$subPanels.length) {
104-
if (isInputEvent(e)) return;
105+
if (isTargetInputLike(e.target)) return;
105106
keys = [...keys, e.key].slice(-10);
106107
resetKeys();
107108
}

src/lib/commandCenter/commands.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,11 @@ const commandsEnabled = derived(disabledMap, ($disabledMap) => {
100100
return Array.from($disabledMap.values()).every((disabled) => !disabled);
101101
});
102102

103-
function isInputEvent(event: KeyboardEvent) {
104-
return ['INPUT', 'TEXTAREA', 'SELECT'].includes((event.target as HTMLElement).tagName);
103+
export function isTargetInputLike(element: EventTarget | null) {
104+
if (!(element instanceof HTMLElement)) return false;
105+
return !!element.closest(
106+
'input,textarea,select,[contenteditable],[role="combobox"],[role="textbox"],[role="searchbox"],[data-command-center-ignore]'
107+
);
105108
}
106109

107110
function getCommandRank(command: KeyedCommand) {
@@ -204,7 +207,12 @@ export const commandCenterKeyDownHandler = derived(
204207
for (const command of commandsArr) {
205208
if (!isKeyedCommand(command)) continue;
206209
if (!command.forceEnable) {
207-
if (command.disabled || !enabled || isInputEvent(event) || $wizard.show) {
210+
if (
211+
command.disabled ||
212+
!enabled ||
213+
isTargetInputLike(event.target) ||
214+
$wizard.show
215+
) {
208216
continue;
209217
}
210218
}

src/lib/commandCenter/searchers/organizations.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import { base } from '$app/paths';
33
import { sdk } from '$lib/stores/sdk';
44
import type { Searcher } from '../commands';
55
import { isCloud } from '$lib/system';
6+
import { Query } from '@appwrite.io/console';
67

78
export const orgSearcher = (async (query: string) => {
89
const { teams } = !isCloud
910
? await sdk.forConsole.teams.list()
10-
: await sdk.forConsole.billing.listOrganization();
11+
: await sdk.forConsole.billing.listOrganization([Query.equal('platform', 'appwrite')]);
1112

1213
return teams
1314
.filter((organization) => organization.name.toLowerCase().includes(query.toLowerCase()))

src/lib/components/archiveProject.svelte

Lines changed: 14 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
<script lang="ts">
22
import { Button, InputText } from '$lib/elements/forms';
3-
import { DropList, GridItem1, CardContainer, Modal } from '$lib/components';
3+
import { GridItem1, CardContainer, Modal } from '$lib/components';
44
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
55
import {
66
Badge,
77
Icon,
88
Typography,
9-
Tag,
109
Accordion,
1110
ActionMenu,
1211
Popover,
@@ -20,7 +19,6 @@
2019
IconFlutter,
2120
IconReact,
2221
IconUnity,
23-
IconInfo,
2422
IconDotsHorizontal,
2523
IconInboxIn,
2624
IconSwitchHorizontal,
@@ -29,7 +27,6 @@
2927
import { getPlatformInfo } from '$lib/helpers/platform';
3028
import { Status, type Models } from '@appwrite.io/console';
3129
import type { ComponentType } from 'svelte';
32-
import { BillingPlan } from '$lib/constants';
3330
import { goto } from '$app/navigation';
3431
import { base } from '$app/paths';
3532
import { sdk } from '$lib/stores/sdk';
@@ -52,8 +49,9 @@
5249
5350
let { projectsToArchive, organization, currentPlan }: Props = $props();
5451
55-
// Track Read-only info droplist per archived project
56-
let readOnlyInfoOpen = $state<Record<string, boolean>>({});
52+
// Check if current plan order is less than Pro (order < 1 means FREE plan)
53+
let isPlanBelowPro = $derived(currentPlan?.order < 1);
54+
5755
let showUnarchiveModal = $state(false);
5856
let projectToUnarchive = $state<Models.Project | null>(null);
5957
let showDeleteModal = $state(false);
@@ -97,7 +95,7 @@
9795
function isUnarchiveDisabled(): boolean {
9896
if (!organization || !currentPlan) return true;
9997
100-
if (organization.billingPlan === BillingPlan.FREE) {
98+
if (isPlanBelowPro) {
10199
const currentProjectCount = organization.projects?.length || 0;
102100
const projectLimit = currentPlan.projects || 0;
103101
@@ -196,10 +194,15 @@
196194

197195
{#if projectsToArchive.length > 0}
198196
<div class="archive-projects-margin-top">
199-
<Accordion title="Archived projects" badge={`${projectsToArchive.length}`}>
197+
<Accordion
198+
title={isPlanBelowPro ? 'Archived projects' : 'Pending archive'}
199+
badge={`${projectsToArchive.length}`}>
200200
<Typography.Text tag="p" size="s">
201-
These projects have been archived and are read-only. You can view and migrate their
202-
data.
201+
{#if isPlanBelowPro}
202+
These projects are archived and require a plan upgrade to restore access.
203+
{:else}
204+
These projects will be archived at the end of your billing cycle.
205+
{/if}
203206
</Typography.Text>
204207

205208
<div class="archive-projects-margin">
@@ -216,36 +219,6 @@
216219
<svelte:fragment slot="title">{formatted}</svelte:fragment>
217220
<svelte:fragment slot="status">
218221
<div class="status-container">
219-
<DropList
220-
bind:show={readOnlyInfoOpen[project.$id]}
221-
placement="bottom-start"
222-
noArrow>
223-
<Tag
224-
size="s"
225-
style="white-space: nowrap;"
226-
on:click={(e) => {
227-
e.preventDefault();
228-
e.stopPropagation();
229-
readOnlyInfoOpen = {
230-
...readOnlyInfoOpen,
231-
[project.$id]: !readOnlyInfoOpen[project.$id]
232-
};
233-
}}>
234-
<Icon icon={IconInfo} size="s" />
235-
<span>Read only</span>
236-
</Tag>
237-
<svelte:fragment slot="list">
238-
<li
239-
class="drop-list-item u-width-250"
240-
style="padding: var(--space-5, 12px) var(--space-6, 16px)">
241-
<span class="u-block u-mb-8">
242-
Archived projects are read-only. You can view
243-
and migrate their data, but they no longer
244-
accept edits or requests.
245-
</span>
246-
</li>
247-
</svelte:fragment>
248-
</DropList>
249222
<Popover let:toggle padding="none" placement="bottom-end">
250223
<Button
251224
text
@@ -267,6 +240,7 @@
267240
>Unarchive project</ActionMenu.Item.Button>
268241
<ActionMenu.Item.Button
269242
leadingIcon={IconSwitchHorizontal}
243+
disabled={isUnarchiveDisabled()}
270244
on:click={() => handleMigrateProject(project)}
271245
>Migrate project</ActionMenu.Item.Button>
272246
<div class="action-menu-divider">

src/lib/components/emptyCardImageCloud.svelte

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
1-
<script>
2-
import { getNextTier, tierToPlan } from '$lib/stores/billing';
1+
<script lang="ts">
2+
import { isSmallViewport } from '$lib/stores/viewport';
33
import { organization } from '$lib/stores/organization';
4+
import { getNextTier, tierToPlan } from '$lib/stores/billing';
45
import { Card, Layout, Typography } from '@appwrite.io/pink-svelte';
56
67
export let source = 'empty_state_card';
8+
export let responsive = false;
9+
10+
// type def for Layout.Stack!
11+
let direction: 'column' | 'row' | 'row-reverse' | 'column-reverse' = 'row';
12+
13+
$: direction = responsive ? ($isSmallViewport ? 'column' : 'row') : 'row';
714
</script>
815

916
<Card.Base variant="secondary" padding="s" radius="s">
10-
<Layout.Stack direction="row" gap="l">
17+
<Layout.Stack {direction} gap="l">
1118
{#if $$slots?.image}
1219
<div style="flex-shrink:0">
1320
<slot name="image" />

src/lib/components/expirationInput.svelte

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,10 @@
134134
value = expirationSelect === 'custom' ? expirationCustom : expirationSelect;
135135
}
136136
137-
value = toLocaleDateISO(new Date(value).getTime());
137+
// Only convert to ISO date if value is not null
138+
if (value !== null) {
139+
value = toLocaleDateISO(new Date(value).getTime());
140+
}
138141
}
139142
140143
$: helper =

src/lib/constants.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,14 +253,13 @@ export const scopes: {
253253
},
254254
{
255255
scope: 'indexes.read',
256-
description: "Access to read your project's database collection's indexes",
256+
description: "Access to read your project's database table's indexes",
257257
category: 'Database',
258258
icon: 'database'
259259
},
260260
{
261261
scope: 'indexes.write',
262-
description:
263-
"Access to create, update, and delete your project's database collection's indexes",
262+
description: "Access to create, update, and delete your project's database table's indexes",
264263
category: 'Database',
265264
icon: 'database'
266265
},

src/lib/elements/forms/inputSelect.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
helper={error ?? helper}
5757
{required}
5858
state={error ? 'error' : 'default'}
59+
data-command-center-ignore
5960
on:invalid={handleInvalid}
6061
on:input
6162
on:change

src/lib/stores/billing.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export function tierToPlan(tier: Tier) {
100100
case BillingPlan.ENTERPRISE:
101101
return tierEnterprise;
102102
default:
103-
return tierFree;
103+
return tierCustom;
104104
}
105105
}
106106

@@ -557,7 +557,8 @@ export async function checkForMissingPaymentMethod() {
557557
const orgs = await sdk.forConsole.billing.listOrganization([
558558
Query.notEqual('billingPlan', BillingPlan.FREE),
559559
Query.isNull('paymentMethodId'),
560-
Query.isNull('backupPaymentMethodId')
560+
Query.isNull('backupPaymentMethodId'),
561+
Query.equal('platform', 'appwrite')
561562
]);
562563
if (orgs?.total) {
563564
orgMissingPaymentMethod.set(orgs.teams[0]);

0 commit comments

Comments
 (0)