diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f717e83ca6..91137d4ee1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -364,6 +364,10 @@ importers: version: 4.0.16(@types/node@24.10.4)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(jsdom@25.0.1)(msw@2.12.4(@types/node@24.10.4)(typescript@5.9.3))(terser@5.44.1)(yaml@2.8.2) svelte: + dependencies: + '@crates-io/api-client': + specifier: workspace:* + version: link:../packages/crates-io-api-client devDependencies: '@chromatic-com/storybook': specifier: 4.1.3 diff --git a/svelte/package.json b/svelte/package.json index d82ad4e24e..a21d4acb2c 100644 --- a/svelte/package.json +++ b/svelte/package.json @@ -21,6 +21,9 @@ "storybook": "storybook dev -p 6006", "build-storybook": "storybook build" }, + "dependencies": { + "@crates-io/api-client": "workspace:*" + }, "devDependencies": { "@chromatic-com/storybook": "4.1.3", "@eslint/compat": "2.0.0", diff --git a/svelte/src/lib/components/frontpage/CrateLists.stories.svelte b/svelte/src/lib/components/frontpage/CrateLists.stories.svelte new file mode 100644 index 0000000000..f7f3ab3f41 --- /dev/null +++ b/svelte/src/lib/components/frontpage/CrateLists.stories.svelte @@ -0,0 +1,91 @@ + + + + + diff --git a/svelte/src/lib/components/frontpage/CrateLists.svelte b/svelte/src/lib/components/frontpage/CrateLists.svelte new file mode 100644 index 0000000000..a5d4680fd2 --- /dev/null +++ b/svelte/src/lib/components/frontpage/CrateLists.svelte @@ -0,0 +1,141 @@ + + +
+ + {#snippet item(crate: Crate, index: number)} + + {/snippet} + + + + {#snippet item(crate: Crate, index: number)} + + {/snippet} + + + + {#snippet item(crate: Crate, index: number)} + + {/snippet} + + + + {#snippet item(crate: Crate, index: number)} + + {/snippet} + + + + {#snippet item(keyword: Keyword)} + + {/snippet} + + + + {#snippet item(category: Category)} + + {/snippet} + +
+ + diff --git a/svelte/src/lib/components/frontpage/ErrorState.stories.svelte b/svelte/src/lib/components/frontpage/ErrorState.stories.svelte new file mode 100644 index 0000000000..2c642b9c24 --- /dev/null +++ b/svelte/src/lib/components/frontpage/ErrorState.stories.svelte @@ -0,0 +1,15 @@ + + + alert('Retry clicked!') }} /> + + diff --git a/svelte/src/lib/components/frontpage/ErrorState.svelte b/svelte/src/lib/components/frontpage/ErrorState.svelte new file mode 100644 index 0000000000..f762281d47 --- /dev/null +++ b/svelte/src/lib/components/frontpage/ErrorState.svelte @@ -0,0 +1,38 @@ + + +

+ Unfortunately something went wrong while loading the crates.io summary data. Feel free to try again, or let the + crates.io team + know if the problem persists. +

+ + + + diff --git a/svelte/src/lib/components/frontpage/HeroButtons.stories.svelte b/svelte/src/lib/components/frontpage/HeroButtons.stories.svelte new file mode 100644 index 0000000000..d2d6170b91 --- /dev/null +++ b/svelte/src/lib/components/frontpage/HeroButtons.stories.svelte @@ -0,0 +1,13 @@ + + + diff --git a/svelte/src/lib/components/frontpage/HeroButtons.svelte b/svelte/src/lib/components/frontpage/HeroButtons.svelte new file mode 100644 index 0000000000..48d9a80750 --- /dev/null +++ b/svelte/src/lib/components/frontpage/HeroButtons.svelte @@ -0,0 +1,36 @@ + + + + + diff --git a/svelte/src/lib/components/frontpage/IntroBlurb.stories.svelte b/svelte/src/lib/components/frontpage/IntroBlurb.stories.svelte new file mode 100644 index 0000000000..1e692ecbe5 --- /dev/null +++ b/svelte/src/lib/components/frontpage/IntroBlurb.stories.svelte @@ -0,0 +1,29 @@ + + + + + diff --git a/svelte/src/lib/components/frontpage/IntroBlurb.svelte b/svelte/src/lib/components/frontpage/IntroBlurb.svelte new file mode 100644 index 0000000000..c01dc0b48a --- /dev/null +++ b/svelte/src/lib/components/frontpage/IntroBlurb.svelte @@ -0,0 +1,67 @@ + + +
+
+ Instantly publish your crates and install them. Use the API to interact and find out more information about + available crates. Become a contributor and enhance the site with your work. +
+ +
+ + +
+
+ + diff --git a/svelte/src/lib/components/frontpage/ListItem.stories.svelte b/svelte/src/lib/components/frontpage/ListItem.stories.svelte new file mode 100644 index 0000000000..e0466b456f --- /dev/null +++ b/svelte/src/lib/components/frontpage/ListItem.stories.svelte @@ -0,0 +1,27 @@ + + + + + + + diff --git a/svelte/src/lib/components/frontpage/ListItem.svelte b/svelte/src/lib/components/frontpage/ListItem.svelte new file mode 100644 index 0000000000..5b03cfb079 --- /dev/null +++ b/svelte/src/lib/components/frontpage/ListItem.svelte @@ -0,0 +1,89 @@ + + + + +
+
{title}
+ {#if subtitle}
{subtitle}
{/if} +
+ +
+ + diff --git a/svelte/src/lib/components/frontpage/ListItemPlaceholder.stories.svelte b/svelte/src/lib/components/frontpage/ListItemPlaceholder.stories.svelte new file mode 100644 index 0000000000..63f2dd2a53 --- /dev/null +++ b/svelte/src/lib/components/frontpage/ListItemPlaceholder.stories.svelte @@ -0,0 +1,15 @@ + + + + + diff --git a/svelte/src/lib/components/frontpage/ListItemPlaceholder.svelte b/svelte/src/lib/components/frontpage/ListItemPlaceholder.svelte new file mode 100644 index 0000000000..45e0255c0b --- /dev/null +++ b/svelte/src/lib/components/frontpage/ListItemPlaceholder.svelte @@ -0,0 +1,62 @@ + + +
+
+ + {#if withSubtitle} + + {/if} +
+ +
+ + diff --git a/svelte/src/lib/components/frontpage/ListSection.svelte b/svelte/src/lib/components/frontpage/ListSection.svelte new file mode 100644 index 0000000000..62f206059d --- /dev/null +++ b/svelte/src/lib/components/frontpage/ListSection.svelte @@ -0,0 +1,52 @@ + + +
+ +

{title}

+ + {#if !items} + {#each { length: 10 } as _, i (i)} +
  • + {/each} + {:else} + {#each items as it, index (it.id)} +
  • {@render item(it, index)}
  • + {/each} + {/if} +
    +
    + + diff --git a/svelte/src/lib/components/frontpage/StatsValue.stories.svelte b/svelte/src/lib/components/frontpage/StatsValue.stories.svelte new file mode 100644 index 0000000000..f01a88c5a4 --- /dev/null +++ b/svelte/src/lib/components/frontpage/StatsValue.stories.svelte @@ -0,0 +1,23 @@ + + + + + diff --git a/svelte/src/lib/components/frontpage/StatsValue.svelte b/svelte/src/lib/components/frontpage/StatsValue.svelte new file mode 100644 index 0000000000..1e4c7160cd --- /dev/null +++ b/svelte/src/lib/components/frontpage/StatsValue.svelte @@ -0,0 +1,52 @@ + + +
    + {value} + {label} +
    + + diff --git a/svelte/src/routes/+page.svelte b/svelte/src/routes/+page.svelte index 1a0164136a..f259ddd9f9 100644 --- a/svelte/src/routes/+page.svelte +++ b/svelte/src/routes/+page.svelte @@ -1,4 +1,38 @@ -

    Welcome to SvelteKit

    -

    - Visit svelte.dev/docs/kit to read the documentation -

    + + + + +{#await data.summary} + + {#if isFirstLoad} + + + {:else} + + + {/if} +{:then summary} + + +{:catch _error} + + +{/await} diff --git a/svelte/src/routes/+page.ts b/svelte/src/routes/+page.ts new file mode 100644 index 0000000000..bd4bf7f323 --- /dev/null +++ b/svelte/src/routes/+page.ts @@ -0,0 +1,38 @@ +import type { operations } from '@crates-io/api-client'; + +import { browser } from '$app/environment'; +import { createClient } from '@crates-io/api-client'; + +type SummaryResponse = operations['get_summary']['responses']['200']['content']['application/json']; + +let cachedSummary: SummaryResponse | undefined; + +/** + * Load function to fetch summary data for the page. + * + * The summary data is cached on the client side to avoid redundant network requests + * and is streamed to the page instead of waiting for the entire data to be fetched + * before rendering. + */ +export async function load({ fetch }) { + const client = createClient({ fetch }); + + return { summary: fetchSummary(client) }; +} + +async function fetchSummary(client: ReturnType): Promise { + if (browser && cachedSummary) { + return cachedSummary; + } + + const response = await client.GET('/api/v1/summary'); + if (response.error) { + throw new Error('Failed to fetch summary data'); + } + + if (browser) { + cachedSummary = response.data; + } + + return response.data; +} diff --git a/svelte/src/routes/page.svelte.spec.ts b/svelte/src/routes/page.svelte.spec.ts deleted file mode 100644 index 8069a8bc1e..0000000000 --- a/svelte/src/routes/page.svelte.spec.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { render } from 'vitest-browser-svelte'; -import { page } from 'vitest/browser'; - -import Page from './+page.svelte'; - -describe('/+page.svelte', () => { - it('should render h1', async () => { - render(Page); - - const heading = page.getByRole('heading', { level: 1 }); - await expect.element(heading).toBeInTheDocument(); - }); -});