Skip to content

next: organization and account notifications improvements #1930

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 51 commits into from
Aug 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
dae56de
Uses HashSet for user organization and roles.
niemyjski Jul 19, 2025
debb3a1
Adds organization management links to UI
niemyjski Jul 19, 2025
5317a9f
Includes invites in user count
niemyjski Jul 19, 2025
a518116
Updates organization links to use A component
niemyjski Jul 19, 2025
ba70a17
WIP - Account notifications
niemyjski Jul 21, 2025
66f9b29
updated svelte
niemyjski Jul 21, 2025
e68650c
Updates dev dependencies
niemyjski Jul 22, 2025
7cd7e9f
WIP account notification page updates
niemyjski Jul 22, 2025
9e8cae8
Uses TimeProvider for testable delays
niemyjski Jul 23, 2025
470df66
WIP: User notification settings.
niemyjski Jul 23, 2025
819c2e6
Fixes form rendering before data is loaded
niemyjski Jul 23, 2025
d918f4f
Updates Beast Mode chatmode to v3.1
niemyjski Jul 25, 2025
b6d36c0
Restructures notification settings forms
niemyjski Jul 25, 2025
320dcfd
Improves user notification settings UI
niemyjski Jul 25, 2025
b808051
Improves appearance settings layout
niemyjski Jul 27, 2025
19ea21c
Updates package-lock.json and package.json
niemyjski Jul 27, 2025
cb35c98
Refactors notification settings forms
niemyjski Jul 28, 2025
e7c0dc8
Uses Alert variants for styling
niemyjski Jul 28, 2025
1e92655
Uses SvelteMap and SvelteDate for reactivity
niemyjski Jul 29, 2025
53f4290
Updates notification settings form behavior
niemyjski Jul 29, 2025
d3cec84
Improves Svelte form handling and data management
niemyjski Jul 30, 2025
c796e2c
Enables Svelte experimental async components
niemyjski Jul 30, 2025
f07e154
Updated deps
niemyjski Jul 30, 2025
b428f53
Updates dependencies in package.json
niemyjski Aug 5, 2025
81d7fe0
Merge branch 'main' into feature/organization-improvements
niemyjski Aug 5, 2025
82c90ee
Added shadcn-svelte calendar
niemyjski Aug 6, 2025
6a2bfab
Documents frontend coding conventions
niemyjski Aug 6, 2025
701ebf5
Renames DropDown component to Dropdown
niemyjski Aug 6, 2025
d9fa424
Refactors organization API and components
niemyjski Aug 6, 2025
c6306d1
WIP - Organization Admin Actions
niemyjski Aug 6, 2025
20fdb27
next: perfectionist enum sorting rules.
niemyjski Aug 7, 2025
ecba07c
Updates placeholder text in dialogs
niemyjski Aug 7, 2025
624d895
Merge branch 'main' into feature/organization-improvements
niemyjski Aug 7, 2025
0801241
Documents Superforms dialog patterns
niemyjski Aug 8, 2025
622753b
Updates dependencies
niemyjski Aug 8, 2025
062fec7
Handles server-side validation errors for bonus events
niemyjski Aug 8, 2025
eae854a
Handles server-side errors in dialog forms
niemyjski Aug 8, 2025
a9d80c1
Merge branch 'main' into feature/organization-improvements
niemyjski Aug 8, 2025
02b1162
Refactors project instructions for clarity
niemyjski Aug 9, 2025
ab82028
Updated deps
niemyjski Aug 9, 2025
5c74e92
Upgraded shadcn-svelte components
niemyjski Aug 9, 2025
d9bfb7f
Adds form ID to superform instances
niemyjski Aug 9, 2025
bf5ad46
Fixes facet filter builder errors
niemyjski Aug 9, 2025
daf49a3
Removes unused pie chart component
niemyjski Aug 9, 2025
69f4397
Replaces card components with typography components
niemyjski Aug 9, 2025
d7d5af4
Adds form IDs for Superforms
niemyjski Aug 9, 2025
506e444
Removes unnecessary key block.
niemyjski Aug 9, 2025
d9bab72
Update deps
niemyjski Aug 10, 2025
142f484
Updates Node.js version to 24
niemyjski Aug 10, 2025
f0e9f65
lint fix
niemyjski Aug 10, 2025
734a597
Updates node version in Dockerfile
niemyjski Aug 10, 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
121 changes: 0 additions & 121 deletions .github/chatmodes/4.1-Beast.chatmode.md

This file was deleted.

12 changes: 6 additions & 6 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

This project features an **ASP.NET Core** backend (REST API) and a **Svelte 5 TypeScript** frontend (SPA).

## Key Principles

All contributions must respect existing formatting and conventions specified in the `.editorconfig` file. You are a distinguished engineer and are expected to deliver high-quality code that adheres to the guidelines in the instruction files.

Let's keep pushing for clarity, usability, and excellence—both in code and user experience.

## Instructions Organization

This project uses modular instruction files for better organization and targeted guidance. The instructions are organized as follows:
Expand All @@ -16,9 +22,3 @@ This project uses modular instruction files for better organization and targeted
- **[Frontend Svelte Testing](instructions/frontend-svelte-testing.instructions.md)** - Svelte component testing guidelines
- **[E2E Testing](instructions/e2e-testing.instructions.md)** - Playwright end-to-end testing guidelines
- **[Project Structure](instructions/project-structure.instructions.md)** - Understanding the codebase organization

## Key Principles

All contributions must respect existing formatting and conventions specified in the `.editorconfig` file. You are a distinguished engineer and are expected to deliver high-quality code that adheres to the guidelines in the instruction files.

Let's keep pushing for clarity, usability, and excellence—both in code and user experience.
3 changes: 1 addition & 2 deletions .github/instructions/backend-testing.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ applyTo: "tests/**/*.cs"

- Write complete, runnable tests—no placeholders or TODOs.
- Use clear, descriptive naming conventions for test methods:
- `MethodName_StateUnderTest_ExpectedBehavior`
- `Should_ExpectedBehavior_When_StateUnderTest`
- `MethodName_StateUnderTest_ExpectedBehavior`
- Follow AAA pattern (Arrange, Act, Assert).

## Test Organization
Expand Down
1 change: 0 additions & 1 deletion .github/instructions/backend.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ applyTo: "**/*.cs"
## Conventions & Best Practices

- Adhere to the `.editorconfig` file and Microsoft's [coding conventions](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions).
- Follow Microsoft's [unit testing best practices](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices).

## Architectural Considerations

Expand Down
178 changes: 178 additions & 0 deletions .github/instructions/frontend-svelte.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,142 @@ applyTo: "src/Exceptionless.Web/ClientApp/**/*.svelte"
- Use `import { page } from '$app/state'` instead of `'$app/stores'`
- Use snippets `{#snippet ...}` and `{@render ...}` instead of `<slot>` for content projection.

## Asynchronous Components (Experimental)

**Available in Svelte 5.36+ with experimental.async compiler option**

You can now use `await` directly in three places:
- At the top level of a component `<script>`
- In a `$derived` expression
- In template expressions (markup)

## Form Handling with Superforms

Always supply a unique, stable `id` option for every `superForm` instance (e.g. `id: 'login'`, `id: 'update-user'`, `id: 'invite-user'`). Missing ids lead to duplicate form data warnings when multiple forms (including dialogs) are present. Use short, kebab-case resource-action names and never reuse the same id on the same page.

### Safe Data Cloning Pattern
Always use the `structuredCloneState()` utility when initializing forms and resetting form data to prevent cache mutation and reactive entanglement:

```svelte
import { structuredCloneState } from '$features/shared/utils/state';

// Form initialization - use structuredCloneState utility
const form = superForm(defaults(structuredCloneState(settings) || new NotificationSettings(), classvalidatorClient(NotificationSettings)), {
// form options...
});

// Form reset in $effect - use structuredCloneState utility
$effect(() => {
if (!$submitting && !$tainted && settings !== previousSettingsRef) {
const clonedSettings = structuredCloneState(settings);
form.reset({ data: clonedSettings, keepMessage: true });
previousSettingsRef = settings;
}
});
```

### Reactive Binding Pattern
For simple reactive bindings to query data, you can override derived values for binding:

```svelte
// Derived value that can be temporarily overridden for binding
let emailNotificationsEnabled = $derived(meQuery.data?.email_notifications_enabled ?? false);

// Sync the derived value when source data changes
$effect(() => {
emailNotificationsEnabled = meQuery.data?.email_notifications_enabled ?? false;
});
```

```svelte
<!-- Direct binding works - temporarily overrides derived value -->
<Switch bind:checked={emailNotificationsEnabled} />
```

**Note:** This pattern uses Svelte 5's ability to override derived values (available since v5.25). The derived value automatically recalculates when dependencies change, but can be temporarily overridden for UI binding. The `$effect` ensures the local state resyncs when the source data changes.

### Superforms onUpdate Pattern for Dialogs

When using `superForm` inside dialogs, always use the following `onUpdate` pattern to ensure server-side validation errors are applied and dialogs don't close prematurely. This also prevents SvelteKit from stealing focus on success.

```svelte
const form = superForm(defaults(new MyForm(), classvalidatorClient(MyForm)), {
dataType: 'json',
id: 'my-form-id',
async onUpdate({ form, result }) {
if (!form.valid) {
return;
}

try {
await doAction(form.data);

open = false;

// HACK: Prevent SvelteKit from stealing focus
result.type = 'failure';
} catch (error: unknown) {
if (error instanceof ProblemDetails) {
applyServerSideErrors(form, error);
result.status = error.status ?? 500;
} else {
result.status = 500;
}
}
},
SPA: true,
validators: classvalidatorClient(MyForm)
});
```

Requirements:
- Import and use `ProblemDetails` from `@exceptionless/fetchclient` and `applyServerSideErrors` from `$features/shared/validation`.
- Close the dialog with `open = false` only after the action succeeds.
- Set `result.type = 'failure'` after success to avoid focus theft.

### Dialog Action Functions Must Rethrow

When passing action functions into dialogs (e.g., `save`, `suspend`, `setBonus`), if you display a toast in a `catch` block, you must rethrow the error so the dialog’s `onUpdate` handler can catch it and apply server-side validation errors.

Example:

```ts
async function setBonus(params: PostSetBonusOrganizationParams) {
toast.dismiss(toastId);
try {
await setOrganizationBonus.mutateAsync(params);
toast.success('Successfully set the organization bonus.');
} catch (error) {
const message = error instanceof ProblemDetails ? error.title : 'Please try again.';
toast.error(`An error occurred while trying to set the organization bonus: ${message}`);
throw error; // critical: propagate to form
}
}
```

### Why These Patterns?
- **Prevents Cache Mutation**: `structuredCloneState()` creates independent copies that don't affect cached data
- **Reactive Safety**: Uses `$state.snapshot()` internally for non-reactive snapshots, preventing unintended dependencies
- **Form Isolation**: Each form gets its own copy of data, preventing cross-contamination
- **Auto-Reset**: Local state automatically resets when source data changes
- **Bindable**: Creates writable state for form controls and UI components
- **Predictable Behavior**: Ensures consistent form state management across all scenarios
- **Type Safety**: Utility provides proper TypeScript types and handles undefined/null gracefully

### Reference Comparison for Resets
Use object reference comparison instead of JSON stringification for performance:
```svelte
// ✅ Good - Reference comparison
if (settings !== previousSettingsRef) {
// reset logic
}

// ❌ Avoid - JSON comparison (slower)
if (JSON.stringify(settings) !== JSON.stringify(previousSettings)) {
// reset logic
}
```

## Event Handling

- All single-line control statements must be enclosed in curly braces
Expand All @@ -24,6 +160,48 @@ applyTo: "src/Exceptionless.Web/ClientApp/**/*.svelte"
- Use the Composite Component Pattern
- Organize components within vertical slices aligned with API controllers

## Dialog Component Patterns

### Naming Conventions
- Dialog state variables should use `open[ComponentName]Dialog` pattern (e.g., `openSuspendOrganizationDialog`, `openMarkStackDiscardedDialog`)
- Avoid generic names like `showDialog` or `isOpen`

### Event Handlers
- Use inline arrow functions for opening dialogs: `onclick={() => (openDialogName = true)}`
- Avoid creating separate handler functions just to set state to true
- Create separate async functions only for complex operations (API calls, validation, etc.)

### Conditional Rendering
- Always wrap dialogs in `{#if}` blocks: `{#if openDialogName} <Dialog /> {/if}`
- This prevents unnecessary DOM creation and improves performance

### API Integration
- Import and use existing interface types from API files (e.g., `SuspendOrganizationParams`)
- Don't create inline types when proper interfaces exist
- Create options files following the `DropdownItem<EnumType>[]` pattern in `options.ts`

### Example Pattern
```svelte
<script lang="ts">
import type { ApiParamsInterface } from '$features/module/api.svelte';
import { optionsArray } from '$features/module/options';

let openMyActionDialog = $state(false);

async function performAction(params: ApiParamsInterface) {
// API call logic here
}
</script>

<Button onclick={() => (openMyActionDialog = true)}>
Action Label
</Button>

{#if openMyActionDialog}
<MyActionDialog bind:open={openMyActionDialog} action={performAction} />
{/if}
```

## Accessibility

- Ensure excellent keyboard navigation for all interactions
Expand Down
Loading