Skip to content
Open
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
98 changes: 98 additions & 0 deletions src/lib/components/DocsCategoryNav.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<script lang="ts">
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import { Icon } from '@steeze-ui/svelte-icon';
import { ArrowLeft } from '@steeze-ui/carbon-icons';
import DocsMobileCategoryMenu from '$lib/components/DocsMobileCategoryMenu.svelte';

type Category = 'account-billing' | 'colocation' | 'vps';
type NavItem = {
label: string;
href: string;
};

const categoryLabels: Record<Category, string> = {
'account-billing': 'Account & Billing',
colocation: 'Colocation',
vps: 'VPS'
};

const pages = import.meta.glob('/src/routes/docs/*/*/+page.svx', {
eager: true,
query: '?raw',
import: 'default'
}) as Record<string, string>;

function titleFromSource(source: string, slug: string): string {
const title = source.match(/<DocsMeta[\s\S]*?title=["']([^"']+)["']/)?.[1];
if (title) return title;

return slug
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}

function discoverCategory(category: Category, order: readonly string[]): NavItem[] {
const discovered = Object.entries(pages)
.filter(([path]) => path.includes(`/docs/${category}/`))
.map(([path, source]) => {
const slug = path.split('/').at(-2);
return slug
? {
label: titleFromSource(source, slug),
href: `/docs/${category}/${slug}`
}
: null;
})
.filter((item): item is NavItem => item !== null);

const bySlug = new Map(discovered.map((item) => [item.href.split('/').at(-1), item]));
const ordered = order
.map((slug) => bySlug.get(slug))
.filter((item): item is NavItem => item !== undefined);
const explicitlyOrdered = new Set(order);

return ordered.concat(
discovered
.filter((item) => !explicitlyOrdered.has(item.href.split('/').at(-1) ?? ''))
.sort((a, b) => a.label.localeCompare(b.label))
);
}

let { category, order }: { category: Category; order: readonly string[] } = $props();
const label = $derived(categoryLabels[category]);
const nav = $derived(discoverCategory(category, order));
</script>

<aside class="hidden w-56 shrink-0 lg:block">
<div class="sticky {category === 'account-billing' ? 'top-0' : 'top-22'} px-6 py-8">
<p class="mb-3 text-[10px] font-medium tracking-widest text-fyra-gray-300 uppercase">{label}</p>
<nav class="flex flex-col gap-0.5" aria-label={`${label} documentation`}>
{#each nav as item (item.href)}
<a
href={resolve(item.href)}
class="rounded-sm px-2 py-1.5 text-sm transition-colors {$page.url.pathname === item.href
? 'bg-fyra-gray-800 font-medium text-fyra-gray-50'
: 'text-fyra-gray-300 hover:text-fyra-gray-100'}"
>
{item.label}
</a>
{/each}
</nav>

<div class="mt-8 border-t border-fyra-gray-800 pt-6">
<a
href={resolve('/docs')}
class="flex items-center gap-1.5 text-xs text-fyra-gray-300 transition-colors hover:text-fyra-gray-100"
>
<Icon src={ArrowLeft} class="h-3 w-3" aria-hidden="true" />
All docs
</a>
</div>
</div>
</aside>

<div class="px-6 pt-6 lg:hidden">
<DocsMobileCategoryMenu category={label} {nav} />
</div>
4 changes: 3 additions & 1 deletion src/lib/remote/colocation-reserve.remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ async function sendDiscordNotification(webhookUrl: string, webhookBody: unknown)
});

if (!response.ok) {
throw new Error(`Response status: ${response.status}, Response content: ${await response.text()}`);
throw new Error(
`Response status: ${response.status}, Response content: ${await response.text()}`
);
}
}

Expand Down
15 changes: 9 additions & 6 deletions src/routes/docs/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
| '/docs/colocation/remote-hands'
| '/docs/colocation/power-budgets'
| '/docs/colocation/networking'
| '/docs/vps/getting-started'
| '/docs/vps/about'
| '/docs/vps/ssh'
| '/docs/vps/user-setup'
| '/docs/vps/hardening'
| '/docs/vps/networking'
| '/docs/vps/choosing-a-distro'
| '/docs/account-billing/support';
| '/docs/account-billing/support'
| '/docs/account-billing/how-we-bill';

type Step = {
n: string;
Expand Down Expand Up @@ -89,10 +92,11 @@
category: 'VPS',
description: 'First login, initial hardening, and setting up your environment.',
articles: [
{ label: 'About Stack VPS', soon: false, href: '/docs/vps/about' },
{ label: 'Getting Started', soon: false, href: '/docs/vps/getting-started' },
{ label: 'Connecting via SSH', soon: false, href: '/docs/vps/ssh' },
{ label: 'Adding a Non-Root User', soon: false, href: '/docs/vps/user-setup' },
{ label: 'Initial Server Hardening', soon: false, href: '/docs/vps/hardening' },
{ label: 'VPS Networking', soon: false, href: '/docs/vps/networking' },
{ label: 'Choosing a Linux Distribution', soon: false, href: '/docs/vps/choosing-a-distro' }
]
},
Expand All @@ -115,10 +119,9 @@
category: 'Account & Billing',
description: 'Managing your plan, invoices, and cancellation.',
articles: [
{ label: 'Upgrading your plan', soon: true },
{ label: 'Reading your invoice', soon: true },
{ label: 'Cancelling a plan', soon: true },
{ label: 'Contacting support', soon: false, href: '/docs/account-billing/support' }
{ label: 'Contacting Support', soon: false, href: '/docs/account-billing/support' },
{ label: 'How We Bill', soon: true },
{ label: 'Upgrading your plan', soon: true }
]
}
];
Expand Down
46 changes: 5 additions & 41 deletions src/routes/docs/account-billing/+layout.svelte
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
<script lang="ts">
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import { Icon } from '@steeze-ui/svelte-icon';
import { ArrowLeft } from '@steeze-ui/carbon-icons';
import { docsProseClass } from '$lib/docs-prose-class';
import DocsMobileCategoryMenu from '$lib/components/DocsMobileCategoryMenu.svelte';
import DocsCategoryNav from '$lib/components/DocsCategoryNav.svelte';

const { children } = $props();

const nav = [{ label: 'Contacting Support', href: '/docs/account-billing/support' }] as const;
const order = ['support'];
</script>

<div class="border-b border-fyra-gray-800 px-6 py-3 lg:px-8">
Expand All @@ -19,44 +16,11 @@
</nav>
</div>

<div class="flex min-h-screen divide-x divide-fyra-gray-800">
<!-- Sidebar -->
<aside class="hidden w-56 shrink-0 lg:block">
<div class="sticky top-0 px-6 py-8">
<p class="mb-3 text-[10px] font-medium tracking-widest text-fyra-gray-300 uppercase">
Account & Billing
</p>
<nav class="flex flex-col gap-0.5" aria-label="Account and billing documentation">
{#each nav as item (item.href)}
<a
href={resolve(item.href)}
class="rounded-sm px-2 py-1.5 text-sm transition-colors {$page.url.pathname ===
item.href
? 'bg-fyra-gray-800 font-medium text-fyra-gray-50'
: 'text-fyra-gray-300 hover:text-fyra-gray-100'}"
>
{item.label}
</a>
{/each}
</nav>

<div class="mt-8 border-t border-fyra-gray-800 pt-6">
<a
href={resolve('/docs')}
class="flex items-center gap-1.5 text-xs text-fyra-gray-300 transition-colors hover:text-fyra-gray-100"
>
<Icon src={ArrowLeft} class="h-3 w-3" aria-hidden="true" />
All docs
</a>
</div>
</div>
</aside>
<div class="flex min-h-screen flex-col divide-fyra-gray-800 lg:flex-row lg:divide-x">
<DocsCategoryNav category="account-billing" {order} />

<!-- Content -->
<div class="min-w-0 flex-1 px-6 pt-6 pb-12 lg:px-12 lg:py-12">
<!-- Mobile nav -->
<DocsMobileCategoryMenu category="Account & Billing" {nav} />

<div class="min-w-0 flex-1 px-6 pt-0 pb-12 lg:px-12 lg:py-12">
<div class={docsProseClass}>
{@render children()}
</div>
Expand Down
2 changes: 2 additions & 0 deletions src/routes/docs/account-billing/support/+page.svx
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@
Thanks for using Fyra Stack!

Currently, you can contact support by emailing [support@fyrastack.com](mailto:support@fyrastack.com).

Please note that we are a small team, so we may not be able to respond to all requests immediately, but we will get back as soon as we can.
52 changes: 5 additions & 47 deletions src/routes/docs/colocation/+layout.svelte
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
<script lang="ts">
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import { Icon } from '@steeze-ui/svelte-icon';
import { ArrowLeft } from '@steeze-ui/carbon-icons';
import { docsProseClass } from '$lib/docs-prose-class';
import DocsMobileCategoryMenu from '$lib/components/DocsMobileCategoryMenu.svelte';
import DocsCategoryNav from '$lib/components/DocsCategoryNav.svelte';

const { children } = $props();

const nav = [
{ label: 'Shipping Hardware to Us', href: '/docs/colocation/shipping-hardware' },
{ label: 'Accessing IPMI Remotely', href: '/docs/colocation/ipmi' },
{ label: 'Requesting Remote Hands', href: '/docs/colocation/remote-hands' },
{ label: 'Power Budgets and PDUs', href: '/docs/colocation/power-budgets' },
{ label: 'Colocation Networking', href: '/docs/colocation/networking' }
] as const;
const order = ['shipping-hardware', 'ipmi', 'remote-hands', 'power-budgets', 'networking'];
</script>

<div class="border-b border-fyra-gray-800 px-6 py-3 lg:px-8">
Expand All @@ -25,44 +16,11 @@
</nav>
</div>

<div class="flex min-h-screen divide-x divide-fyra-gray-800">
<!-- Sidebar -->
<aside class="hidden w-56 shrink-0 lg:block">
<div class="sticky top-[calc(var(--spacing)*22)] px-6 py-8">
<p class="mb-3 text-[10px] font-medium tracking-widest text-fyra-gray-300 uppercase">
Colocation
</p>
<nav class="flex flex-col gap-0.5" aria-label="Colocation documentation">
{#each nav as item (item.href)}
<a
href={resolve(item.href)}
class="rounded-sm px-2 py-1.5 text-sm transition-colors {$page.url.pathname ===
item.href
? 'bg-fyra-gray-800 font-medium text-fyra-gray-50'
: 'text-fyra-gray-300 hover:text-fyra-gray-100'}"
>
{item.label}
</a>
{/each}
</nav>

<div class="mt-8 border-t border-fyra-gray-800 pt-6">
<a
href={resolve('/docs')}
class="flex items-center gap-1.5 text-xs text-fyra-gray-300 transition-colors hover:text-fyra-gray-100"
>
<Icon src={ArrowLeft} class="h-3 w-3" aria-hidden="true" />
All docs
</a>
</div>
</div>
</aside>
<div class="flex min-h-screen flex-col divide-fyra-gray-800 lg:flex-row lg:divide-x">
<DocsCategoryNav category="colocation" {order} />

<!-- Content -->
<div class="min-w-0 flex-1 px-6 pt-6 pb-12 lg:px-12 lg:py-12">
<!-- Mobile nav -->
<DocsMobileCategoryMenu category="Colocation" {nav} />

<div class="min-w-0 flex-1 px-6 pt-0 pb-12 lg:px-12 lg:py-12">
<div class={docsProseClass}>
{@render children()}
</div>
Expand Down
50 changes: 5 additions & 45 deletions src/routes/docs/vps/+layout.svelte
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
<script lang="ts">
import { resolve } from '$app/paths';
import { page } from '$app/stores';
import { Icon } from '@steeze-ui/svelte-icon';
import { ArrowLeft } from '@steeze-ui/carbon-icons';
import { docsProseClass } from '$lib/docs-prose-class';
import DocsMobileCategoryMenu from '$lib/components/DocsMobileCategoryMenu.svelte';
import DocsCategoryNav from '$lib/components/DocsCategoryNav.svelte';

const { children } = $props();

const nav = [
{ label: 'Connecting via SSH', href: '/docs/vps/ssh' },
{ label: 'Adding a non-root user', href: '/docs/vps/user-setup' },
{ label: 'Initial server hardening', href: '/docs/vps/hardening' },
{ label: 'VPS Networking', href: '/docs/vps/networking' },
{ label: 'Choosing a Linux Distribution', href: '/docs/vps/choosing-a-distro' }
] as const;
const order = ['about', 'getting-started', 'ssh', 'user-setup', 'hardening', 'choosing-a-distro'];
</script>

<div class="border-b border-fyra-gray-800 px-6 py-3 lg:px-8">
Expand All @@ -25,42 +16,11 @@
</nav>
</div>

<div class="flex min-h-screen divide-x divide-fyra-gray-800">
<!-- Sidebar -->
<aside class="hidden w-56 shrink-0 lg:block">
<div class="sticky top-[calc(var(--spacing)*22)] px-6 py-8">
<p class="mb-3 text-[10px] font-medium tracking-widest text-fyra-gray-300 uppercase">VPS</p>
<nav class="flex flex-col gap-0.5" aria-label="VPS documentation">
{#each nav as item (item.href)}
<a
href={resolve(item.href)}
class="rounded-sm px-2 py-1.5 text-sm transition-colors {$page.url.pathname ===
item.href
? 'bg-fyra-gray-800 font-medium text-fyra-gray-50'
: 'text-fyra-gray-300 hover:text-fyra-gray-100'}"
>
{item.label}
</a>
{/each}
</nav>

<div class="mt-8 border-t border-fyra-gray-800 pt-6">
<a
href={resolve('/docs')}
class="flex items-center gap-1.5 text-xs text-fyra-gray-300 transition-colors hover:text-fyra-gray-100"
>
<Icon src={ArrowLeft} class="h-3 w-3" aria-hidden="true" />
All docs
</a>
</div>
</div>
</aside>
<div class="flex min-h-screen flex-col divide-fyra-gray-800 lg:flex-row lg:divide-x">
<DocsCategoryNav category="vps" {order} />

<!-- Content -->
<div class="min-w-0 flex-1 px-6 pt-6 pb-12 lg:px-12 lg:py-12">
<!-- Mobile nav -->
<DocsMobileCategoryMenu category="VPS" {nav} />

<div class="min-w-0 flex-1 px-6 pt-0 pb-12 lg:px-12 lg:py-12">
<div class={docsProseClass}>
{@render children()}
</div>
Expand Down
Loading
Loading