Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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: 2 additions & 0 deletions apps/storybook/.storybook/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ function applyThemeClass(name) {

docElement.classList.remove('azion', 'azion-light', 'azion-dark')
docElement.classList.add(...(THEME_CLASSES[name] || THEME_CLASSES.dark))
docElement.dataset.theme = THEME_CLASSES[name] ? name : 'dark'
}

applyThemeClass('dark')
Expand Down Expand Up @@ -93,6 +94,7 @@ export const parameters = {
'Documentation',
['Overview', 'Authoring', 'Example Page'],
'Templates',
['Marketing', ['Overview', 'Social Proof', 'Heroes', 'Content', 'Network', 'Media']],
'Marketing',
'Site'
]
Expand Down
84 changes: 84 additions & 0 deletions apps/storybook/src/stories/_shared/code-samples.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
const IMPORT = 'import'

const FILE_UPLOAD = `${IMPORT} type { AzionBucketObject, AzionStorageResponse } from "azion/storage";
${IMPORT} { createObject } from "azion/storage";
${IMPORT} { Hono } from "hono";

const app = new Hono();

app.post("/upload", async (c) => {
const body = await c.req.parseBody();
const file = body["file"]; // File | string

// First check if file is a valid File object
if (
!file ||
typeof file !== "object" ||
typeof file.arrayBuffer !== "function"
) {
return c.json({ message: "Invalid file" }, 400);
}

const { data: newObject } = (await createObject({
bucket: "uploads",
key: file.name,
content: await file.arrayBuffer(),
})) as AzionStorageResponse<AzionBucketObject>;

return c.json({ key: newObject?.key }, 201, {
"Content-Type": file.type,
"Content-Disposition": \`attachment; filename="\${newObject?.key}"\`,
"Content-Length": newObject?.size?.toString() ?? "0",
});
});

export default app;`

const TRANSACTIONAL_EMAIL = `${IMPORT} { Hono } from "hono";

const app = new Hono();

app.post("/welcome", async (c) => {
const { email, name } = await c.req.json();

// The API key lives in the workload's environment, never in the bundle
const sent = await fetch("https://api.provider.com/v1/messages", {
method: "POST",
headers: {
Authorization: \`Bearer \${Azion.env.get("EMAIL_API_KEY")}\`,
"Content-Type": "application/json",
},
body: JSON.stringify({
to: email,
subject: "Welcome aboard",
text: \`Hi \${name}, your account is ready.\`,
}),
});

if (!sent.ok) {
return c.json({ message: "Could not send the email" }, 502);
}

return c.json({ queued: true }, 202);
});

export default app;`

export const FUNCTION_SAMPLE_TABS = [
{
label: 'File upload',
value: 'file-upload',
language: 'typescript',
code: FILE_UPLOAD,
fileName: 'github.com/aziontech/azion-samples',
fileIcon: 'pi pi-github'
},
{
label: 'Send transactional emails',
value: 'transactional-email',
language: 'typescript',
code: TRANSACTIONAL_EMAIL,
fileName: 'github.com/aziontech/azion-samples',
fileIcon: 'pi pi-github'
}
]
31 changes: 31 additions & 0 deletions apps/storybook/src/stories/_shared/markup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export const indent = (markup, depth = 1) =>
markup
.split('\n')
.map((line) => (line ? `${' '.repeat(depth)}${line}` : line))
.join('\n')

export const each = (items, render, depth = 0) =>
items.map((item, index) => indent(render(item, index), depth)).join('\n')

export const inColumn = (markup) =>
`<SectionContainer max-width="site">\n <SectionGap hatch />\n\n${indent(markup)}\n\n <SectionGap hatch />\n</SectionContainer>`

export const COLUMN_IMPORTS = [
"import SectionContainer from '@aziontech/webkit/section-container'",
"import SectionGap from '@aziontech/webkit/section-gap'"
]

const text = (value) => `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`

const multiline = (value) =>
`\`${value.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${')}\``

export const declareTabs = (name, tabs) =>
`const ${name} = [\n${tabs
.map(
(tab) =>
` {\n${Object.entries(tab)
.map(([key, value]) => ` ${key}: ${key === 'code' ? multiline(value) : text(value)}`)
.join(',\n')}\n }`
)
.join(',\n')}\n]`
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import CardGrid from '@aziontech/webkit/card-grid'
import FrameBox from '@aziontech/webkit/frame-box'
import SectionContainer from '@aziontech/webkit/section-container'
import SectionGap from '@aziontech/webkit/section-gap'
import SectionModule from '@aziontech/webkit/section-module'
import Topic from '@aziontech/webkit/topic'

import { COLUMN_IMPORTS, each, inColumn } from '../../_shared/markup'
import { toSfc } from '../../_shared/story-source'

const IMPORTS = [
"import CardGrid from '@aziontech/webkit/card-grid'",
"import FrameBox from '@aziontech/webkit/frame-box'",
...COLUMN_IMPORTS,
"import SectionModule from '@aziontech/webkit/section-module'",
"import Topic from '@aziontech/webkit/topic'"
]

const components = {
CardGrid,
'CardGrid.Cell': CardGrid.Cell,
FrameBox,
SectionContainer,
SectionGap,
SectionModule,
Topic
}

const RETAIL = [
{
icon: 'pi pi-bolt',
title: 'Speed that converts',
description:
'Deliver fast storefront experiences with consistent page speed and Core Web Vitals, lifting conversion, SEO visibility, and average order value across web and mobile.'
},
{
icon: 'pi pi-chart-line',
title: 'Built for peak season',
description:
'Stay online during Black Friday, Cyber Monday, and flash sales with automatic scaling and a distributed architecture that absorbs traffic surges without overprovisioning.'
},
{
icon: 'pi pi-shield',
title: 'Security built in',
description:
'Protect transactions, customer data, and storefront availability with DDoS mitigation, WAF, and zero trust controls integrated into the same distributed architecture.'
},
{
icon: 'pi pi-lock',
title: 'Fraud and bots blocked',
description:
'Block bots, scraping, credential stuffing, account takeover, and payment fraud with bot management and application security applied before requests reach your origin.'
},
{
icon: 'pi pi-sparkles',
title: 'AI-powered personalization',
description:
'Deliver tailored recommendations, search, and content with AI Inference and Functions executed at request time, improving conversion without round-tripping to centralized origins.'
},
{
icon: 'pi pi-credit-card',
title: 'Lower costs at scale',
description:
'Offload origin traffic, optimize images, and serve cached responses from a distributed architecture to cut cloud, egress, and CDN costs while scaling automatically with demand.'
}
]

const FINANCIAL = [
{
icon: 'pi pi-server',
title: 'High availability',
description:
'Ensure operational continuity for latency-sensitive financial applications and APIs, with automatic scalability and real-time responses, even during transaction peaks.'
},
{
icon: 'pi pi-shield',
title: 'Advanced security',
description:
'Protect financial data and transactions with multilayer security integrated into the application, mitigating API attacks, DDoS, and bots without increasing operational complexity.'
},
{
icon: 'pi pi-check-circle',
title: 'Continuous compliance',
description:
'Meet regulatory requirements with consistent security policies and continuous auditing across distributed environments, without compromising performance or development agility.'
}
]

const capabilityGrid = (items) =>
inColumn(`<SectionModule :divided="false" :padded="false">
<FrameBox flush borders="y" marks="bottom">
<CardGrid flush kind="frame" :columns="3">
${each(
items,
(item) => `<CardGrid.Cell kind="canvas">
<Topic
:heading-level="2"
icon="${item.icon}"
title="${item.title}"
description="${item.description}"
/>
</CardGrid.Cell>`,
3
)}
</CardGrid>
</FrameBox>
</SectionModule>`)

const SIX_TEMPLATE = capabilityGrid(RETAIL)
const THREE_TEMPLATE = capabilityGrid(FINANCIAL)

const meta = {
title: 'Templates/Marketing/Content/CapabilityGrid',
tags: ['autodocs'],
parameters: {
layout: 'fullscreen',
backgrounds: { default: 'dark' },
controls: { disable: true },
docs: {
description: {
component:
'The band that answers "what does this do for me" right under a solution page’s hero: three or six capabilities, each an icon, a title and one sentence, on a hairline frame grid. Every solution page opens its column with it (Retail, Web Apps, AI Workloads, Security, Performance, Streaming, Financial Services, Technology). Built from `SectionModule`, `FrameBox`, `CardGrid` and `Topic`.'
},
canvas: { sourceState: 'shown' }
}
}
}

export default meta

export const SixCapabilities = {
render: () => ({ components, template: SIX_TEMPLATE }),
parameters: {
docs: {
description: {
story:
'Two rows of three, the Retail page’s set. Six is the count a page uses when it argues on breadth.'
},
source: { code: toSfc(IMPORTS, SIX_TEMPLATE) }
}
}
}

export const ThreeCapabilities = {
render: () => ({ components, template: THREE_TEMPLATE }),
parameters: {
docs: {
description: {
story:
'One row of three, the Financial Services page’s set, for a page that argues on a few strong claims.'
},
source: { code: toSfc(IMPORTS, THREE_TEMPLATE) }
}
}
}
Loading