Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
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 .claude/agents/xbyk-component-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: xbyk-component-reference
description: Quick reference guide for native Xperience by Kentico admin components. Consult when choosing components, understanding XbyK design system, or ensuring consistent UI patterns.
tools: Read, mcp__kentico-docs-mcp__kentico_docs_search, mcp__kentico-docs-mcp__kentico_docs_fetch
model: haiku
color: teal
color: pink
---

You are a quick reference guide for native Xperience by Kentico (XbyK) admin UI components from `@kentico/xperience-admin-components`.
Expand Down
42 changes: 41 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ C:\Projects\xperience-community-sustainability\

**XbyK Components Used**:
- Native: `Card`, `Button`, `Stack`, `Row`, `Column`, `Headline`, `Spacing`, `Icon`
- Patterns: `usePageCommand` hook for backend commands, `inProgress` prop for loading states
- Patterns: `usePageCommand` hook for backend commands, manual `useState` for loading states, `inProgress` and `disabled` props on buttons
- Responsive: `colsLg`/`colsMd` breakpoints for grid layout

**File Organization**:
Expand All @@ -190,6 +190,46 @@ C:\Projects\xperience-community-sustainability\
}
```

**usePageCommand Hook - Loading State Pattern**:
- **IMPORTANT**: `usePageCommand` does NOT provide a built-in `inProgress` property
- **Return type**: Only returns `{ execute: (data?: TCommandData) => void }`
- **Loading states must be managed manually** using `useState`
- **Correct pattern**:
```typescript
// 1. Create manual state
const [isLoading, setIsLoading] = useState(false);

// 2. Configure usePageCommand with after/onError callbacks
const { execute: myCommand } = usePageCommand<ResponseType>(
Commands.MyCommand,
{
after: (response) => {
// Process response...
setIsLoading(false); // Stop loading when done
},
onError: (err) => {
console.error(err);
setIsLoading(false); // Stop loading on error
},
}
);

// 3. Set loading state before executing
const handleClick = () => {
setIsLoading(true);
myCommand();
};

// 4. Use state for button props
<Button
label="Run Command"
onClick={handleClick}
disabled={isLoading}
inProgress={isLoading}
/>
```
- **Examples in codebase**: See `isLoading` (line 26), `isExportingPdf` (line 27), and `isLoadingMore` (line 38) in SustainabilityTabTemplate.tsx

### 4. JavaScript Analysis (src/wwwroot/scripts/resource-checker.js)

**Process**:
Expand Down
19 changes: 11 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,6 @@ A community-driven open-source package that brings sustainability insights and a

> For more details about this package, check out [Bringing Sustainability Insights to Xperience by Kentico](https://www.goldfinch.me/blog/bringing-sustainability-insights-to-xperience-by-kentico) which provides more background information around the package and its origin.

## Screenshots

Once installed, a new tab appears for each page in your web channels. The Sustainability tab allows content editors and marketers to see and benchmark page weight and carbon emissions, which is then converted to a carbon rating for individual pages.

<a href="/src/images/SustainabilityReport-PageTab.jpeg">
<img src="/src/images/SustainabilityReport-PageTab.jpeg" width="800" alt="Sustainability Tab for pages in Xperience by Kentico">
</a>

## Library Version Matrix

| Xperience Version | Library Version |
Expand Down Expand Up @@ -83,18 +75,29 @@ The hosting status is displayed in the Sustainability report with three possible

### Sustainability Report

Once installed, a new tab appears for each page in your web channels. The Sustainability tab allows content editors and marketers to see and benchmark page weight and carbon emissions, which is then converted to a carbon rating for individual pages.

<a href="/src/images/SustainabilityReport-PageTab.jpeg">
<img src="/src/images/SustainabilityReport-PageTab.jpeg" width="800" alt="Sustainability Tab for pages in Xperience by Kentico">
</a>

Each report includes:

- **Carbon Rating**: Letter grade (A+ through F) based on grams CO₂ per page view
- **CO₂ Emissions**: Total carbon emissions with hosting status indicator
- **Page Weight**: Total size of all resources loaded
- **Resource Breakdown**: Categorized by type (Images, Scripts, CSS, etc.) with individual file sizes
- **Optimization Tips**: Xperience-specific recommendations for reducing page weight
- **PDF Export**: Download complete sustainability reports as PDF for sharing and documentation

### Historical Tracking

Track sustainability improvements over time with comprehensive historical reporting:

<a href="/src/images/SustainabilityReport-ReportHistory.jpeg">
<img src="/src/images/SustainabilityReport-ReportHistory.jpeg" width="800" alt="Historical tracking with trend charts and report history">
</a>

- **Trend Analysis**: Two side-by-side charts showing CO₂ emissions and page weight trends over time
- **Report History**: View all previous sustainability reports for a page with collapsible cards showing key metrics
- **Pagination**: Load historical reports in batches to efficiently browse through your sustainability history
Expand Down
1 change: 1 addition & 0 deletions src/Client/dist/entry.kxh.92ad6aa5b360df56a5fa.js

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion src/Client/dist/entry.kxh.d81750fa880d7e2c5f70.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const SustainabilityTabTemplate = (
props: SustainabilityTabTemplateProps | null
) => {
const [isLoading, setIsLoading] = useState(false);
const [isExportingPdf, setIsExportingPdf] = useState(false);
const [error, setError] = useState<string | null>(null);
const [data, setData] = useState<SustainabilityData | undefined | null>(
props?.sustainabilityData
Expand All @@ -36,7 +37,9 @@ export const SustainabilityTabTemplate = (
);
const [isLoadingMore, setIsLoadingMore] = useState(false);
// Initialize hasMoreHistory from backend
const [hasMoreHistory, setHasMoreHistory] = useState(props?.hasMoreHistory ?? false);
const [hasMoreHistory, setHasMoreHistory] = useState(
props?.hasMoreHistory ?? false
);
const [showHistory, setShowHistory] = useState(false);
const [nextPageIndex, setNextPageIndex] = useState(1); // Initial load got page 0, next is page 1

Expand Down Expand Up @@ -90,6 +93,39 @@ export const SustainabilityTabTemplate = (
},
});

const exportPdf = usePageCommand<{ pdfBase64: string; fileName: string }>(
Commands.ExportReportAsPdf,
{
after: (response) => {
if (response) {
// Convert base64 to blob and trigger download
const byteCharacters = atob(response.pdfBase64);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], { type: "application/pdf" });

// Create download link
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = response.fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
}
setIsExportingPdf(false);
},
onError: (err) => {
console.error("PDF export error:", err);
setIsExportingPdf(false);
},
}
);

if (data === undefined || data === null) {
return (
<div style={{ padding: "32px", maxWidth: "1400px", margin: "0 auto" }}>
Expand Down Expand Up @@ -198,22 +234,37 @@ export const SustainabilityTabTemplate = (
label="Back to Current Report"
color={ButtonColor.Secondary}
size={ButtonSize.M}
icon="xp-arrow-left"
onClick={() => setShowHistory(false)}
/>
) : (
<>
<Button
label="Export as PDF"
color={ButtonColor.Secondary}
size={ButtonSize.M}
icon="xp-file-pdf"
disabled={isExportingPdf}
inProgress={isExportingPdf}
onClick={() => {
setIsExportingPdf(true);
exportPdf.execute();
}}
/>
{historicalReports.length > 0 && (
<Button
label="View History"
color={ButtonColor.Secondary}
size={ButtonSize.M}
icon="xp-clock"
onClick={() => setShowHistory(true)}
/>
)}
<Button
label="Run New Analysis"
color={ButtonColor.Primary}
size={ButtonSize.M}
icon="xp-rotate-right"
disabled={isLoading}
inProgress={isLoading}
onClick={() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ export const ResourceGroupCard = ({ group, totalPageSize }: ResourceGroupCardPro
<div style={resourceGroupCardStyles.expandButtonContainer}>
<Button
label={expanded ? "Show less" : `Show ${group.resources.length - 3} more`}
trailingIcon={expanded ? "xp-chevron-up" : "xp-chevron-down"}
onClick={() => setExpanded(!expanded)}
color={ButtonColor.Secondary}
size={ButtonSize.S}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export const HistoryView = ({
label="Load More History"
color={ButtonColor.Secondary}
size={ButtonSize.M}
trailingIcon="xp-chevron-down"
disabled={isLoadingMore}
inProgress={isLoadingMore}
onClick={() => onLoadMore(nextPageIndex)}
Expand Down
1 change: 1 addition & 0 deletions src/Client/src/Sustainability/tab-template/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ export interface SustainabilityTabTemplateProps {
export const Commands = {
RunReport: "RunReport",
LoadMoreHistory: "LoadMoreHistory",
ExportReportAsPdf: "ExportReportAsPdf",
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public static IServiceCollection AddXperienceCommunitySustainability(this IServi
services.Configure<SustainabilityOptions>(configuration.GetSection("Sustainability"));

services.AddScoped<ISustainabilityService, SustainabilityService>();
services.AddScoped<ISustainabilityPdfService, SustainabilityPdfService>();
services.AddScoped<IContentHubLinkService, ContentHubLinkService>();
services.AddSingleton<ISustainabilityModuleInstaller, SustainabilityModuleInstaller>();

Expand Down
Loading