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
52 changes: 52 additions & 0 deletions apps/tangle-cloud/netlify.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,32 @@
publish = "dist/apps/tangle-cloud"
command = "yarn nx build tangle-cloud"

# ─── Per-context environment ───────────────────────────────────────────
#
# Vite reads VITE_* vars at build time and inlines them into the bundle,
# so flipping one of these requires a rebuild — not a runtime config
# change. Setting the iframe kill-switch here keeps the source of truth
# in-repo and reviewable. Override per-branch by adding [context.<name>]
# blocks if we ever need to differ between staging and production.

[context.production.environment]
# Production is iframe-on. Falls back to link-out automatically for any
# blueprint that doesn't satisfy publisher + host + metadata gates, so
# this flag is safe to leave on once the in-app trust gates are doing
# their job. To disarm, flip to "false" and redeploy — single env flip
# mass-disables every iframe app without an app-side change.
VITE_BLUEPRINT_IFRAME_ENABLED = "true"

[context.deploy-preview.environment]
# Preview deploys (PR builds) inherit production's iframe-on default
# so contributors testing iframe-mode manifests on a PR-preview URL
# see the same surface production users will.
VITE_BLUEPRINT_IFRAME_ENABLED = "true"

[context.branch-deploy.environment]
# Same default for any non-master branch deploy (e.g. staging).
VITE_BLUEPRINT_IFRAME_ENABLED = "true"

# If the branch is not master, continue the build process
# If the branch is master, check if the project's CHANGELOG.md file has changed
# If the CHANGELOG.md file has changed, continue the build process,
Expand Down Expand Up @@ -32,3 +58,29 @@
Permissions-Policy = "camera=(), microphone=(), geolocation=(), payment=(), usb=(), bluetooth=(), accelerometer=(), gyroscope=(), magnetometer=(), midi=(), serial=()"
X-Frame-Options = "SAMEORIGIN"
Referrer-Policy = "strict-origin-when-cross-origin"

# ─── Cache headers ──────────────────────────────────────────────────────
#
# Vite emits hashed filenames into /assets, so the file at any given URL
# is content-addressed and safe to mark immutable. Repeat-visit loads
# become near-instant because the browser doesn't even revalidate.
[[headers]]
for = "/assets/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"

# Fonts are also content-addressed when present; mark them immutable and
# permit cross-origin use so they can be loaded from sibling subdomains.
[[headers]]
for = "/fonts/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
Access-Control-Allow-Origin = "*"

# index.html must NOT be cached aggressively — it's the entry point that
# references the hashed asset bundles, so it has to be replaced atomically
# on every deploy.
[[headers]]
for = "/index.html"
[headers.values]
Cache-Control = "public, max-age=0, must-revalidate"
6 changes: 3 additions & 3 deletions apps/tangle-cloud/src/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const PaymentsPoolPage = lazy(() => import('../pages/payments/pool'));
const PaymentsCreditsPage = lazy(() => import('../pages/payments/credits'));
const NotFoundPage = lazy(() => import('../pages/notFound'));

const PageFallback = () => (
const RouteFallback = () => (
<div className="mt-4 space-y-4">
<Skeleton className="h-8 w-48 rounded-md" />
<Skeleton className="h-4 w-96 max-w-full rounded-md" />
Expand All @@ -61,7 +61,7 @@ const PageFallback = () => (
// Wrap lazy page in layout + Suspense so layout (with Header) stays visible during load
const withLayout = (LayoutCmp: FC<{ children: ReactNode }>, Page: FC) => (
<LayoutCmp>
<Suspense fallback={<PageFallback />}>
<Suspense fallback={<RouteFallback />}>
<Page />
</Suspense>
</LayoutCmp>
Expand Down Expand Up @@ -209,7 +209,7 @@ const App: FC = () => {
<Route
path="*"
element={
<Suspense fallback={<PageFallback />}>
<Suspense fallback={<RouteFallback />}>
<NotFoundPage />
</Suspense>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ const IframeAppApprovalModal: FC<Props> = ({
return (
<Dialog
open={pending !== null}
onOpenChange={(open) => {
onOpenChange={(open: boolean) => {
if (!open && !submitting) handleReject();
}}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
Button,
Card,
CardContent,
EmptyState,
} from '@tangle-network/sandbox-ui/primitives';
import { useAccount } from 'wagmi';
import type { JobCall } from '@tangle-network/tangle-shared-ui/data/graphql';
Expand Down Expand Up @@ -192,11 +193,10 @@ const SandboxBlueprintServicePage: FC<Props> = ({ entry, serviceId }) => {
</div>
))
) : (
<div className="rounded-lg border border-dashed border-border bg-muted/30 p-5">
<p className="text-muted-foreground text-sm">
No job activity indexed for this service yet.
</p>
</div>
<EmptyState
title="No job activity yet"
description="No jobs have been indexed for this service yet."
/>
)}
</div>

Expand Down
146 changes: 146 additions & 0 deletions apps/tangle-cloud/src/components/Text.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**
* Tangle Cloud canonical Text component.
*
* Single source of truth for the type ramp across tangle-cloud. Consumes the
* `@tangle-network/brand` 0.3 token system: `--font-sans`, `--font-display`,
* `--font-size-*`, `--line-height-*`, plus the Tailwind-mapped `text-foreground`
* / `text-muted-foreground` colors driven by the brand HSL bridge.
*
* Replaces ~13 per-page `Text` shims that all reimplemented the same ramp with
* subtly different variant unions ('h4' | 'h5' | 'body1' | 'body2' | 'body3' |
* 'body4'). The new variant union mirrors brand vocabulary; legacy variant names
* are accepted as aliases for backwards compatibility, so existing call sites
* keep type-checking.
*/
import type { ComponentProps, ElementType, FC } from 'react';

/**
* Brand-aligned variants. Mapping rationale:
*
* - `h1` → 4xl display (page hero)
* - `h2` → 3xl display (section heading; replaces legacy `h4`)
* - `h3` → xl display (sub-section; replaces legacy `h5`)
* - `h4` → lg display
* - `body-lg` → text-base (replaces legacy `body1`)
* - `body` → text-sm (replaces legacy `body2`, the default)
* - `body-sm` → text-xs (replaces legacy `body3` / `body4`)
* - `caption` → text-[11px] muted (timestamps, meta)
*
* Legacy aliases ('h4', 'h5', 'body1', 'body2', 'body3', 'body4') resolve to
* the same classes the per-page shims used, preserving visual parity.
*/
export type TextVariant =
// Brand-aligned canonical names
| 'h1'
| 'h2'
| 'h3'
| 'h4'
| 'body-lg'
| 'body'
| 'body-sm'
| 'caption'
// Legacy aliases — kept so existing call sites continue to type-check.
// Map: 'h4' → 'h2' (display 3xl), 'h5' → 'h3' (display xl),
// 'body1' → 'body-lg', 'body2' → 'body', 'body3' / 'body4' → 'body-sm'.
| 'h5'
| 'body1'
| 'body2'
| 'body3'
| 'body4';

export type TextProps = ComponentProps<'p'> & {
variant?: TextVariant;
fw?: 'bold' | 'semibold';
/** Override the rendered semantic element. Defaults are sensible per variant. */
as?: ElementType;
};

/**
* Maps a (possibly legacy) variant to the canonical token set. We resolve once
* here so both `defaultElementFor` and `classFor` work off canonical names.
*/
const canonicalize = (
variant: TextVariant,
): Exclude<TextVariant, 'h5' | 'body1' | 'body2' | 'body3' | 'body4'> => {
switch (variant) {
case 'h4':
// Legacy 'h4' was used as a top-level page heading (3xl display).
// Brand 'h2' is the canonical 3xl display.
return 'h2';
case 'h5':
// Legacy 'h5' was used for section headings (xl display).
return 'h3';
case 'body1':
return 'body-lg';
case 'body2':
return 'body';
case 'body3':
case 'body4':
return 'body-sm';
default:
return variant;
}
};

const defaultElementFor = (
variant: ReturnType<typeof canonicalize>,
): ElementType => {
switch (variant) {
case 'h1':
return 'h1';
case 'h2':
return 'h2';
case 'h3':
return 'h3';
case 'h4':
return 'h4';
case 'caption':
return 'span';
default:
return 'p';
}
};

const classFor = (variant: ReturnType<typeof canonicalize>): string => {
switch (variant) {
case 'h1':
return 'font-display text-4xl tracking-tight text-foreground';
case 'h2':
return 'font-display text-3xl tracking-tight text-foreground';
case 'h3':
return 'font-display text-xl text-foreground';
case 'h4':
return 'font-display text-lg text-foreground';
case 'body-lg':
return 'text-base text-foreground';
case 'body':
return 'text-sm text-foreground';
case 'body-sm':
return 'text-xs text-muted-foreground';
case 'caption':
return 'text-[11px] text-muted-foreground';
}
};

export const Text: FC<TextProps> = ({
variant = 'body',
fw,
as,
className = '',
...props
}) => {
const canonical = canonicalize(variant);
const Component = (as ?? defaultElementFor(canonical)) as ElementType;
const variantClass = classFor(canonical);
const weightClass =
fw === 'bold' ? 'font-bold' : fw === 'semibold' ? 'font-semibold' : '';

return (
<Component
className={[variantClass, weightClass, className]
.filter(Boolean)
.join(' ')}
{...props}
/>
);
};
8 changes: 5 additions & 3 deletions apps/tangle-cloud/src/components/TxHistoryDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import useTxHistoryStore, {
import useEvmAddress from '@tangle-network/tangle-shared-ui/hooks/useEvmAddress';
import { useEvmAssetMetadatas } from '@tangle-network/tangle-shared-ui/hooks/useEvmAssetMetadatas';
import { Alert, Button, Chip, Text } from './sandbox/SandboxUi';
import { EmptyState } from '@tangle-network/sandbox-ui/primitives';
import { formatDistanceToNow } from 'date-fns';
import { capitalize } from 'lodash';
import { type FC, useMemo } from 'react';
Expand Down Expand Up @@ -149,9 +150,10 @@ const TxHistoryDrawer: FC = () => {

{relevantTransactions === null ||
relevantTransactions.length === 0 ? (
<Text variant="body2" className="text-muted-foreground">
No transactions yet.
</Text>
<EmptyState
title="No transactions yet"
description="Submitted transactions will appear in this drawer."
/>
) : (
relevantTransactions.map((tx) => (
<TransactionItem key={tx.hash} {...tx} />
Expand Down
50 changes: 11 additions & 39 deletions apps/tangle-cloud/src/components/sandbox/SandboxUi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,14 @@ import {
TabsTrigger,
Textarea,
} from '@tangle-network/sandbox-ui/primitives';
import type { ComponentProps, ElementType, FC, ReactNode } from 'react';
import type { ChangeEvent, ComponentProps, FC, ReactNode } from 'react';

// Re-export the canonical tangle-cloud Text from the dedicated module so we
// preserve the existing import surface (`import { Text } from '...sandbox/SandboxUi'`)
// while routing through a single Text implementation that consumes
// @tangle-network/brand 0.3 tokens.
export { Text } from '../Text';
export type { TextProps, TextVariant } from '../Text';

export {
Badge,
Expand Down Expand Up @@ -123,43 +130,6 @@ export const CircularProgress: FC<{
</span>
);

export type TextProps = ComponentProps<'p'> & {
variant?: 'h4' | 'h5' | 'body1' | 'body2' | 'body3' | 'body4';
fw?: 'bold' | 'semibold';
};

export const Text: FC<TextProps> = ({
variant = 'body2',
fw,
className = '',
...props
}) => {
const Component = (
variant === 'h4' ? 'h1' : variant === 'h5' ? 'h2' : 'p'
) as ElementType;
const variantClass =
variant === 'h4'
? 'font-display text-3xl tracking-tight text-foreground'
: variant === 'h5'
? 'font-display text-xl text-foreground'
: variant === 'body1'
? 'text-base text-foreground'
: variant === 'body3' || variant === 'body4'
? 'text-xs text-muted-foreground'
: 'text-sm text-foreground';
const weightClass =
fw === 'bold' ? 'font-bold' : fw === 'semibold' ? 'font-semibold' : '';

return (
<Component
className={[variantClass, weightClass, className]
.filter(Boolean)
.join(' ')}
{...props}
/>
);
};

export type ButtonProps = Omit<
ComponentProps<typeof SandboxButton>,
'variant' | 'size'
Expand Down Expand Up @@ -252,7 +222,9 @@ export const Input: FC<InputProps> = ({
]
.filter(Boolean)
.join(' ')}
onChange={(event) => onChange?.(event.currentTarget.value)}
onChange={(event: ChangeEvent<HTMLInputElement>) =>
onChange?.(event.currentTarget.value)
}
/>
{leftIcon && (
<div className="pointer-events-none absolute inset-y-0 left-3 flex items-center text-muted-foreground">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { FC } from 'react';
import { Card, Skeleton, Text } from '../../components/sandbox/SandboxUi';
import { Skeleton, Text } from '../../components/sandbox/SandboxUi';
import { EmptyState } from '@tangle-network/sandbox-ui/primitives';
import { useCreditsContext } from '../../app/CreditsProvider';
import CreditAccountCard from '../../components/payments/CreditAccountCard';
import useCreditAccountState from '../../data/payments/useCreditAccountState';
Expand Down Expand Up @@ -69,12 +70,10 @@ const CreditBalanceContainer: FC = () => {
</div>

{creditAccounts.length === 0 ? (
<Card className="text-center py-8">
<Text variant="body1" className="text-muted-foreground">
No credit accounts yet. Fund one from the shielded pool to get
started.
</Text>
</Card>
<EmptyState
title="No credit accounts yet"
description="Fund one from the shielded pool to get started."
/>
) : (
<div className="grid gap-3 sm:grid-cols-2">
{creditAccounts.map((acct) => (
Expand Down
Loading
Loading