Skip to content

Commit

Permalink
Merge branch 'main' into cs-7044-cs-7555-cs-7556-subscription-section…
Browse files Browse the repository at this point in the history
…-in-account-settings
  • Loading branch information
FadhlanR committed Dec 2, 2024
2 parents 62dd164 + 6337348 commit 8ca738f
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 56 deletions.
19 changes: 9 additions & 10 deletions packages/boxel-ui/addon/src/components/input/index.gts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { guidFor } from '@ember/object/internals';
import Component from '@glimmer/component';

import cn from '../../helpers/cn.ts';
import cssVar from '../../helpers/css-var.ts';
import element from '../../helpers/element.ts';
import optional from '../../helpers/optional.ts';
import pick from '../../helpers/pick.ts';
Expand Down Expand Up @@ -185,6 +186,9 @@ export default class BoxelInput extends Component<Signature> {
'search-icon-container'
has-validation=this.hasValidation
}}
style={{cssVar
search-input-icon-color='var(--boxel-input-search-icon-color)'
}}
>
<IconSearch class='search-icon' width='20' height='20' />
</div>
Expand Down Expand Up @@ -292,9 +296,7 @@ export default class BoxelInput extends Component<Signature> {
padding-top: var(--boxel-sp-xxxs);
padding-right: var(--boxel-sp-xl);
padding-bottom: var(--boxel-sp-xxxs);
}
.search.has-validation {
/* to account for the icon being on the left */
padding-right: unset;
padding-left: var(--boxel-sp-xxl); /* leave room for icon */
}
Expand All @@ -303,22 +305,19 @@ export default class BoxelInput extends Component<Signature> {
--boxel-form-control-border-radius: var(--boxel-border-radius-xl)
var(--boxel-border-radius-xl) 0 0;
}
.search-icon {
--icon-color: var(--search-input-icon-color, var(--boxel-highlight));
}
.search-icon-container {
--icon-color: var(--boxel-highlight);
grid-area: post-icon;
grid-area: pre-icon;
display: flex;
height: 100%;
align-items: center;
justify-content: center;
}
.search-icon-container.has-validation {
grid-area: pre-icon;
}
.validation-icon-container {
grid-area: post-icon;
Expand Down
4 changes: 2 additions & 2 deletions packages/experiments-realm/blog-app.gts
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,11 @@ class BlogAppTemplate extends Component<typeof BlogApp> {
@query={{this.query}}
@realms={{this.realms}}
>
<:adminData as |card|>
<:meta as |card|>
{{#if this.showAdminData}}
<BlogAdminData @cardId={{card.url}} />
{{/if}}
</:adminData>
</:meta>
</CardsGrid>
</div>
{{/if}}
Expand Down
24 changes: 13 additions & 11 deletions packages/experiments-realm/components/grid.gts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ interface CardsGridSignature {
format: Format;
};
Blocks: {
adminData: [card: PrerenderedCard];
meta: [card: PrerenderedCard];
};
Element: HTMLElement;
}
Expand All @@ -44,22 +44,24 @@ export class CardsGrid extends GlimmerComponent<CardsGridSignature> {
</:loading>
<:response as |cards|>
{{#each cards key='url' as |card|}}
<li
class='{{@selectedView}}-view-container'
{{@context.cardComponentModifier
cardId=card.url
format='data'
fieldType=undefined
fieldName=undefined
}}
>
<li class='{{@selectedView}}-view-container'>
<CardContainer
{{@context.cardComponentModifier
cardId=card.url
format='data'
fieldType=undefined
fieldName=undefined
}}
class='card'
@displayBoundaries={{not (eq @selectedView 'card')}}
>
<card.component />
</CardContainer>
{{yield card to='adminData'}}
{{#if (eq @selectedView 'card')}}
{{#if (has-block 'meta')}}
{{yield card to='meta'}}
{{/if}}
{{/if}}
</li>
{{/each}}
</:response>
Expand Down
52 changes: 36 additions & 16 deletions packages/host/app/components/operator-mode/stack-item.gts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export default class OperatorModeStackItem extends Component<Signature> {
@tracked private hasUnsavedChanges = false;
@tracked private lastSaved: number | undefined;
@tracked private lastSavedMsg: string | undefined;
@tracked private lastSaveError: Error | undefined;
private refreshSaveMsg: number | undefined;
private subscribedCard: CardDef | undefined;
private contentEl: HTMLElement | undefined;
Expand Down Expand Up @@ -350,28 +351,47 @@ export default class OperatorModeStackItem extends Component<Signature> {
private initiateAutoSaveTask = restartableTask(async () => {
this.hasUnsavedChanges = true;
await timeout(this.environmentService.autoSaveDelayMs);
this.isSaving = true;
await this.args.saveCard(this.card);
this.hasUnsavedChanges = false;
this.isSaving = false;
this.lastSaved = Date.now();
this.calculateLastSavedMsg();
try {
this.isSaving = true;
this.lastSaveError = undefined;
await timeout(25);
await this.args.saveCard(this.card);
this.hasUnsavedChanges = false;
this.lastSaved = Date.now();
} catch (error) {
// error will already be logged in CardService
this.lastSaveError = error as Error;
} finally {
this.isSaving = false;
this.calculateLastSavedMsg();
}
});

private calculateLastSavedMsg() {
// runs frequently, so only change a tracked property if the value has changed
if (this.lastSaved == null) {
if (this.lastSavedMsg) {
this.lastSavedMsg = undefined;
}
} else {
let savedMessage = `Saved ${formatDistanceToNow(this.lastSaved, {
let savedMessage: string | undefined;
if (this.lastSaveError) {
savedMessage = `Failed to save: ${this.getErrorMessage(
this.lastSaveError,
)}`;
} else if (this.lastSaved) {
savedMessage = `Saved ${formatDistanceToNow(this.lastSaved, {
addSuffix: true,
})}`;
if (this.lastSavedMsg != savedMessage) {
this.lastSavedMsg = savedMessage;
}
}
// runs frequently, so only change a tracked property if the value has changed
if (this.lastSavedMsg != savedMessage) {
this.lastSavedMsg = savedMessage;
}
}

private getErrorMessage(error: Error) {
if ((error as any).responseHeaders?.get('x-blocked-by-waf-rule')) {
return 'Rejected by firewall';
}
if (error.message) {
return error.message;
}
return 'Unknown error';
}

private doneEditing = restartableTask(async () => {
Expand Down
5 changes: 1 addition & 4 deletions packages/host/app/services/card-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export default class CardService extends Service {

err.status = response.status;
err.responseText = responseText;
err.responseHeaders = response.headers;

throw err;
}
Expand Down Expand Up @@ -255,12 +256,8 @@ export default class CardService extends Service {
}
return result;
} catch (err) {
// TODO for CS-6268 we'll need to show a visual indicator that the auto
// save has failed. Until that ticket is implemented, the only indication
// of a failed auto-save will be from the console.
console.error(`Failed to save ${card.id}: `, err);
throw err;
return;
} finally {
api.unsubscribeFromChanges(card, onCardChange);
}
Expand Down
77 changes: 67 additions & 10 deletions packages/host/tests/integration/components/operator-mode-test.gts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
fillIn,
focus,
blur,
setupOnerror,
triggerEvent,
triggerKeyEvent,
typeIn,
Expand All @@ -23,6 +22,7 @@ import { Loader } from '@cardstack/runtime-common/loader';
import CardPrerender from '@cardstack/host/components/card-prerender';
import OperatorMode from '@cardstack/host/components/operator-mode/container';

import NetworkService from '@cardstack/host/services/network';
import OperatorModeStateService from '@cardstack/host/services/operator-mode-state-service';

import {
Expand Down Expand Up @@ -548,15 +548,7 @@ module('Integration | operator-mode', function (hooks) {
await click('[data-test-edit-button]');
});

// TODO CS-6268 visual indicator for failed auto-save should build off of this test
test('an error in auto-save is handled gracefully', async function (assert) {
let done = assert.async();

setupOnerror(function (error) {
assert.ok(error, 'expected a global error');
done();
});

await setCardInOperatorModeState(`${testRealmURL}BoomPet/paper`);

await renderComponent(
Expand All @@ -570,7 +562,19 @@ module('Integration | operator-mode', function (hooks) {
await waitFor('[data-test-pet]');
await waitFor('[data-test-edit-button]');
await click('[data-test-edit-button]');
await fillIn('[data-test-field="boom"] input', 'Bad cat!');
fillIn('[data-test-field="boom"] input', 'Bad cat!');
await waitUntil(
() =>
document
.querySelector('[data-test-auto-save-indicator]')
?.textContent?.trim() == 'Saving…',
);
await waitUntil(
() =>
document
.querySelector('[data-test-auto-save-indicator]')
?.textContent?.trim() == 'Failed to save: Boom!',
);
await setCardInOperatorModeState(`${testRealmURL}BoomPet/paper`);

await waitFor('[data-test-pet]');
Expand All @@ -579,6 +583,59 @@ module('Integration | operator-mode', function (hooks) {
assert.dom('[data-test-pet]').includesText('Paper Bad cat!');
});

test('a 403 from Web Appliction Firewall is handled gracefully when auto-saving', async function (assert) {
let networkService = this.owner.lookup('service:network') as NetworkService;
networkService.virtualNetwork.mount(
async (req: Request) => {
if (req.method === 'PATCH' && req.url.includes('test/Pet/buzz')) {
return new Response(
'{ message: "Request blocked by Web Application Firewall. See x-blocked-by-waf-rule response header for detail." }',
{
status: 403,
headers: {
'Content-Type': 'application/json',
'X-Blocked-By-WAF-Rule': 'CrossSiteScripting_BODY',
},
},
);
}
return null;
},
{ prepend: true },
);
await setCardInOperatorModeState(`${testRealmURL}Pet/buzz`);

await renderComponent(
class TestDriver extends GlimmerComponent {
<template>
<OperatorMode @onClose={{noop}} />
<CardPrerender />
</template>
},
);
await waitFor('[data-test-field="name"]');
await waitFor('[data-test-edit-button]');
await click('[data-test-edit-button]');
fillIn('[data-test-field="name"] input', 'Fuzz');
await waitUntil(
() =>
document
.querySelector('[data-test-auto-save-indicator]')
?.textContent?.trim() == 'Saving…',
{ timeoutMessage: 'Waitng for Saving... to appear' },
);
await waitUntil(
() =>
document
.querySelector('[data-test-auto-save-indicator]')
?.textContent?.trim() == 'Failed to save: Rejected by firewall',
{ timeoutMessage: 'Waitng for "Failed to save" to appear' },
);
assert
.dom('[data-test-auto-save-indicator]')
.containsText('Failed to save: Rejected by firewall');
});

test('opens workspace chooser after closing the only remainingcard on the stack', async function (assert) {
await setCardInOperatorModeState(`${testRealmURL}Person/fadhlan`);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
<p style="margin: 0; margin-bottom: 15px;">If you've lost your password or wish to reset it, use the button below to get started.</p>
<p style="margin: 0;">If you did not request a password reset, you can safely ignore this email. Only a person with access to your email can reset your account password.</p>
<div style="margin-top: 35px; width: 100%;">
<a style="padding: 12px 0; font-weight: 600; font-size: 13px; letter-spacing: 0.025em; color: white; background-color: #03c4bf; border: 1px solid #03c4bf; border-radius: 100px; cursor: pointer; text-decoration: none; width:100%; display:block; text-align: center;" href="{{ link }}">Reset Password</a>
<a style="padding: 12px 0; font-weight: 600; font-size: 13px; letter-spacing: 0.025em; color: black; background-color: #00ffba; border: 1px solid #00ffba; border-radius: 100px; cursor: pointer; text-decoration: none; width:100%; display:block; text-align: center;" href="{{ link }}">Reset Password</a>
</div>
{% endblock %}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

<p style="margin: 0;">You have requested to <strong>reset your Boxel account password</strong>. Click the link below to confirm this action. <br /><br />
If you did not mean to do this, please close this page and your password will not be changed.</p>
<p><button type="submit" style="padding: 12px 0; font-family: 'Poppins', 'Open Sans', helvetica, 'Arial', sans-serif; font-weight: 600; font-size: 13px; letter-spacing: 0.025em; color: white; background-color: #00ffba; border: 1px solid #00ffba; border-radius: 100px; cursor: pointer; width:100%;">Confirm changing my password</button></p>
<p><button type="submit" style="padding: 12px 0; font-family: 'Poppins', 'Open Sans', helvetica, 'Arial', sans-serif; font-weight: 600; font-size: 13px; letter-spacing: 0.025em; color: black; background-color: #00ffba; border: 1px solid #00ffba; border-radius: 100px; cursor: pointer; width:100%;">Confirm changing my password</button></p>
</form>
</td>
</tr>
Expand Down
2 changes: 1 addition & 1 deletion packages/matrix/docker/synapse/templates/registration.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
{% block body %}
<p style="margin: 0;">Please confirm that this is your e-mail address by clicking on the button below or use <a href="{{ link }}">this link</a>.</p>
<div style="margin-top: 35px; width: 100%;">
<a style="padding: 12px 0; font-weight: 600; font-size: 13px; letter-spacing: 0.025em; color: white; background-color: #00ffba; border: 1px solid #00ffba; border-radius: 100px; cursor: pointer; text-decoration: none; width:100%; display:block; text-align: center;" href="{{ link }}">Verify Email</a>
<a style="padding: 12px 0; font-weight: 600; font-size: 13px; letter-spacing: 0.025em; color: black; background-color: #00ffba; border: 1px solid #00ffba; border-radius: 100px; cursor: pointer; text-decoration: none; width:100%; display:block; text-align: center;" href="{{ link }}">Verify Email</a>
</div>
{% endblock %}

0 comments on commit 8ca738f

Please sign in to comment.