Skip to content

Commit d8c0a85

Browse files
committed
Merge branch 'develop' into feature/CXIEP-7764
2 parents 126ae4f + a8fb786 commit d8c0a85

File tree

65 files changed

+1520
-299
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+1520
-299
lines changed

Diff for: .github/workflows/pr-title-check.yml

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
name: PR Title Check
2+
3+
on:
4+
pull_request:
5+
types: [opened, edited, synchronize, reopened]
6+
7+
jobs:
8+
check-pr-title:
9+
name: PR - Check PR title
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Validate PR title
13+
id: validate
14+
env:
15+
PR_TITLE: ${{ github.event.pull_request.title }}
16+
run: |
17+
echo "Checking PR title: ${PR_TITLE}"
18+
19+
REGEX='^(docs|feat|fix|perf|refactor|style|test|chore)(\([^)]*\))?:\ .+'
20+
21+
if [[ ! "$PR_TITLE" =~ $REGEX ]]; then
22+
echo "❌ PR title is invalid. Example of a valid title: \`feat: Add user authentication\`, \`chore(deps): update dependency babel-loader to v10\`. Allowed prefixes: docs, feat, fix, perf, refactor, style, test, chore"
23+
exit 1
24+
fi
25+
26+
comment-on-failure:
27+
needs: check-pr-title
28+
if: failure()
29+
runs-on: ubuntu-latest
30+
steps:
31+
- name: Post PR comment on failure
32+
uses: actions/github-script@v7
33+
with:
34+
github-token: ${{ secrets.GITHUB_TOKEN }}
35+
script: |
36+
const issue_number = context.payload.pull_request.number;
37+
const owner = context.repo.owner;
38+
const repo = context.repo.repo;
39+
40+
const commentBody = `# 🚨 PR Title Validation Failed 🚨
41+
42+
Your pull request title does not follow the required format. Please update it to match the expected pattern:
43+
44+
**Expected format:**
45+
\`<type>: <subject>\`
46+
47+
## **Allowed Types**
48+
- **docs:** Changes to documentation only
49+
- **feat:** New feature work
50+
- **fix:** Bug fixes
51+
- **perf:** Code improvements for performance
52+
- **refactor:** Code changes that are not bug fixes or features
53+
- **style:** Code style changes (e.g., whitespace, formatting)
54+
- **test:** Adding or updating tests
55+
- **chore:** Build, CI, scripts, configs, etc.
56+
57+
## **Example of a valid PR title**
58+
✅ \`feat: Add user authentication\`
59+
✅ \`fix: Resolve checkout bug\`
60+
✅ \`docs: Update API documentation\`
61+
62+
❌ **Merge is blocked until the PR title is corrected.**`;
63+
64+
github.rest.issues.createComment({
65+
issue_number: issue_number,
66+
owner: owner,
67+
repo: repo,
68+
body: commentBody
69+
});

Diff for: feature-libs/asm/occ/adapters/occ-asm.adapter.spec.ts

+20-10
Original file line numberDiff line numberDiff line change
@@ -158,13 +158,15 @@ describe('OccAsmAdapter', () => {
158158
result = data;
159159
});
160160
const mockReq: TestRequest = httpMock.expectOne((req) => {
161-
return req.method === 'GET';
161+
return req.method === 'POST';
162162
});
163163

164164
expect(mockReq.request.params.get('baseSite')).toBe(baseSite);
165165
expect(mockReq.request.params.get('sort')).toBe(sort);
166-
expect(mockReq.request.params.get('query')).toBe(searchQuery);
167-
expect(mockReq.request.params.get('customerListId')).toBe(customerListId);
166+
expect(mockReq.request.body).toEqual({
167+
query: searchQuery,
168+
customerListId: customerListId,
169+
});
168170
expect(mockReq.request.params.get('pageSize')).toBe(pageSize + '');
169171

170172
expect(mockReq.cancelled).toBeFalsy();
@@ -196,12 +198,14 @@ describe('OccAsmAdapter', () => {
196198
result = data;
197199
});
198200
const mockReq: TestRequest = httpMock.expectOne((req) => {
199-
return req.method === 'GET';
201+
return req.method === 'POST';
200202
});
201203

202204
expect(mockReq.request.params.get('baseSite')).toBe(baseSite);
203205
expect(mockReq.request.params.get('sort')).toBe(defaultSort);
204-
expect(mockReq.request.params.get('query')).toBe(searchQuery);
206+
expect(mockReq.request.body).toEqual({
207+
query: searchQuery,
208+
});
205209
expect(mockReq.request.params.get('pageSize')).toBe(pageSize + '');
206210

207211
expect(mockReq.cancelled).toBeFalsy();
@@ -228,12 +232,13 @@ describe('OccAsmAdapter', () => {
228232
result = data;
229233
});
230234
const mockReq: TestRequest = httpMock.expectOne((req) => {
231-
return req.method === 'GET';
235+
return req.method === 'POST';
232236
});
233237

234238
expect(mockReq.request.params.get('baseSite')).toBe(baseSite);
235239
expect(mockReq.request.params.get('sort')).toBe(defaultSort);
236240
expect(mockReq.request.params.get('query')).toBeNull();
241+
expect(mockReq.request.body).toEqual({});
237242
expect(mockReq.request.params.get('pageSize')).toBeNull();
238243

239244
expect(mockReq.cancelled).toBeFalsy();
@@ -265,12 +270,14 @@ describe('OccAsmAdapter', () => {
265270
result = data;
266271
});
267272
const mockReq: TestRequest = httpMock.expectOne((req) => {
268-
return req.method === 'GET';
273+
return req.method === 'POST';
269274
});
270275

271276
expect(mockReq.request.params.get('baseSite')).toBe(baseSite);
272277
expect(mockReq.request.params.get('sort')).toBe(defaultSort);
273-
expect(mockReq.request.params.get('query')).toBe(searchQuery);
278+
expect(mockReq.request.body).toEqual({
279+
query: searchQuery,
280+
});
274281
expect(mockReq.request.params.get('pageSize')).toBe(pageSize + '');
275282

276283
expect(mockReq.cancelled).toBeFalsy();
@@ -299,12 +306,15 @@ describe('OccAsmAdapter', () => {
299306
result = data;
300307
});
301308
const mockReq: TestRequest = httpMock.expectOne((req) => {
302-
return req.method === 'GET';
309+
return req.method === 'POST';
303310
});
304311

305312
expect(mockReq.request.params.get('baseSite')).toBe(baseSite);
306313
expect(mockReq.request.params.get('sort')).toBeNull();
307-
expect(mockReq.request.params.get('query')).toBeNull();
314+
315+
expect(mockReq.request.body).toEqual({
316+
customerListId: 'instoreCustomers',
317+
});
308318
expect(mockReq.request.params.get('pageSize')).toBeNull();
309319

310320
expect(mockReq.cancelled).toBeFalsy();

Diff for: feature-libs/asm/occ/adapters/occ-asm.adapter.ts

+22-9
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ import {
3232
import { Observable } from 'rxjs';
3333
import { catchError } from 'rxjs/operators';
3434

35+
interface CustomerSearchParams {
36+
/**
37+
* Supported parameters for searching
38+
*/
39+
query?: string;
40+
orderId?: string;
41+
customerListId?: string;
42+
}
43+
3544
@Injectable()
3645
export class OccAsmAdapter implements AsmAdapter {
3746
private activeBaseSite: string;
@@ -91,6 +100,8 @@ export class OccAsmAdapter implements AsmAdapter {
91100
this.activeBaseSite
92101
);
93102

103+
const searchBody: CustomerSearchParams = {};
104+
94105
if (options.sort !== undefined) {
95106
params = params.set('sort', options.sort);
96107
} else {
@@ -100,10 +111,10 @@ export class OccAsmAdapter implements AsmAdapter {
100111
}
101112

102113
if (options.query !== undefined) {
103-
params = params.set('query', options.query);
114+
searchBody.query = options.query;
104115
}
105116
if (options.orderId !== undefined) {
106-
params = params.set('orderId', options.orderId);
117+
searchBody.orderId = options.orderId;
107118
}
108119

109120
if (options.pageSize !== undefined) {
@@ -115,7 +126,7 @@ export class OccAsmAdapter implements AsmAdapter {
115126
}
116127

117128
if (options.customerListId !== undefined) {
118-
params = params.set('customerListId', options.customerListId);
129+
searchBody.customerListId = options.customerListId;
119130
}
120131

121132
const url = this.occEndpointsService.buildUrl(
@@ -127,12 +138,14 @@ export class OccAsmAdapter implements AsmAdapter {
127138
}
128139
);
129140

130-
return this.http.get<CustomerSearchPage>(url, { headers, params }).pipe(
131-
catchError((error) => {
132-
throw normalizeHttpError(error, this.logger);
133-
}),
134-
this.converterService.pipeable(CUSTOMER_SEARCH_PAGE_NORMALIZER)
135-
);
141+
return this.http
142+
.post<CustomerSearchPage>(url, searchBody, { headers, params })
143+
.pipe(
144+
catchError((error) => {
145+
throw normalizeHttpError(error, this.logger);
146+
}),
147+
this.converterService.pipeable(CUSTOMER_SEARCH_PAGE_NORMALIZER)
148+
);
136149
}
137150

138151
bindCart({ cartId, customerId }: BindCartParams): Observable<unknown> {

Diff for: feature-libs/cart/base/components/cart-shared/cart-item-list-row/cart-item-list-row.component.html

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<div class="cx-info">
2222
<div *ngIf="item.product?.name" class="cx-name">
2323
<a
24+
[attr.id]="item.product?.code + '_header'"
2425
class="cx-link"
2526
[routerLink]="{ cxRoute: 'product', params: item.product } | cxUrl"
2627
>{{ item.product?.name }}</a

Diff for: feature-libs/checkout/base/components/guards/checkout-auth.guard.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
AuthService,
1313
SemanticPathService,
1414
} from '@spartacus/core';
15-
import { combineLatest, Observable } from 'rxjs';
15+
import { Observable, combineLatest } from 'rxjs';
1616
import { filter, map } from 'rxjs/operators';
1717
import { CheckoutConfigService } from '../services/checkout-config.service';
1818

Diff for: integration-libs/opf/checkout/components/opf-checkout-components.module.ts

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import { NgModule } from '@angular/core';
88
import { OpfCheckoutBillingAddressFormModule } from './opf-checkout-billing-address-form/opf-checkout-billing-address-form.module';
9+
import { OpfCheckoutEmailUpdateModule } from './opf-checkout-email-update/opf-checkout-email-update.module';
910
import { OpfCheckoutPaymentAndReviewModule } from './opf-checkout-payment-and-review/opf-checkout-payment-and-review.module';
1011
import { OpfCheckoutPaymentWrapperModule } from './opf-checkout-payment-wrapper/opf-checkout-payment-wrapper.module';
1112
import { OpfCheckoutPaymentsModule } from './opf-checkout-payments/opf-checkout-payments.module';
@@ -16,6 +17,7 @@ import { OpfCheckoutPaymentsModule } from './opf-checkout-payments/opf-checkout-
1617
OpfCheckoutPaymentsModule,
1718
OpfCheckoutBillingAddressFormModule,
1819
OpfCheckoutPaymentWrapperModule,
20+
OpfCheckoutEmailUpdateModule,
1921
],
2022
})
2123
export class OpfCheckoutComponentsModule {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 SAP Spartacus team <[email protected]>
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
export * from './opf-checkout-email-update.component';
8+
export * from './opf-checkout-email-update.module';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<form (ngSubmit)="onSubmit()" [formGroup]="checkoutLoginForm">
2+
<p class="form-legend">
3+
{{ 'formLegend.required' | cxTranslate }}
4+
</p>
5+
<div class="form-group">
6+
<label>
7+
<span class="label-content">
8+
{{ 'checkoutLogin.emailAddress.label' | cxTranslate }}
9+
<ng-template [ngTemplateOutlet]="requiredAsterisk"></ng-template>
10+
</span>
11+
<input
12+
required="true"
13+
type="email"
14+
name="email"
15+
class="form-control"
16+
formControlName="email"
17+
placeholder="{{
18+
'checkoutLogin.emailAddress.placeholder' | cxTranslate
19+
}}"
20+
[attr.aria-describedby]="'emailError'"
21+
/>
22+
23+
<cx-form-errors
24+
id="emailError"
25+
aria-live="off"
26+
[translationParams]="{
27+
label: 'checkoutLogin.emailAddress.label' | cxTranslate,
28+
}"
29+
[control]="checkoutLoginForm.get('email')"
30+
></cx-form-errors>
31+
</label>
32+
</div>
33+
34+
<div class="form-group">
35+
<label>
36+
<span class="label-content">
37+
{{ 'checkoutLogin.confirmEmail.label' | cxTranslate }}
38+
<ng-template [ngTemplateOutlet]="requiredAsterisk"></ng-template>
39+
</span>
40+
<input
41+
required="true"
42+
type="email"
43+
name="emailConfirmation"
44+
class="form-control"
45+
formControlName="emailConfirmation"
46+
placeholder="{{
47+
'checkoutLogin.confirmEmail.placeholder' | cxTranslate
48+
}}"
49+
[attr.aria-describedby]="'emailConfirmationError'"
50+
/>
51+
52+
<cx-form-errors
53+
id="emailConfirmationError"
54+
aria-live="off"
55+
[translationParams]="{
56+
label: 'checkoutLogin.confirmEmail.label' | cxTranslate,
57+
}"
58+
[control]="checkoutLoginForm.get('emailConfirmation')"
59+
></cx-form-errors>
60+
</label>
61+
</div>
62+
63+
<button type="submit" class="btn btn-block btn-primary">
64+
{{ 'checkoutLogin.continue' | cxTranslate }}
65+
</button>
66+
</form>
67+
68+
<ng-template #requiredAsterisk>
69+
<abbr
70+
class="text-decoration-none required-asterisk"
71+
aria-hidden="true"
72+
title="{{ 'common.required' | cxTranslate }}"
73+
>*</abbr
74+
>
75+
</ng-template>

0 commit comments

Comments
 (0)