Skip to content

Commit 225fd4b

Browse files
krish1905alexbodenclaude
authored
Upgrade Nextra from v2 to v3 (#4490)
## Description <!--- Please provide a summary of your changes. Make sure to include relevant motivation, context, and link related documents/conversations. --> This PR upgrades Nextra from v2 to v3 as requested in the GitHub issue. The migration follows the official Nextra v3 migration guide while making only the minimal changes necessary for compatibility. - Updated `nextra` and `nextra-theme-docs` from v2.13.2 to v3 - Converted configuration files to v3 format: - `next.config.js` → `next.config.mjs` (ESM format) - All `_meta.json` → `_meta.js` with export syntax - `_app.mdx` → `_app.tsx` (v3 requirement) - Updated component APIs (`Card` → `Cards.Card`) - Updated `theme.config.tsx` for v3 API changes - Added minimal CSS overrides to preserve the original neutral theme appearance - Fixed math syntax in one MDX file for v3 compatibility Resolves #3428 ## Checklist - [x] I have read and understood the [WATcloud Guidelines](https://cloud.watonomous.ca/docs/community-docs/watcloud/guidelines) - [x] I have performed a self-review of my code --------- Co-authored-by: Alex Boden <alex.boden@uwaterloo.ca> Co-authored-by: Alex Boden <arboden@uwaterloo.ca> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent fd47f96 commit 225fd4b

25 files changed

Lines changed: 23124 additions & 12212 deletions

components/assets.tsx

Lines changed: 52 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import Uppy from '@uppy/core';
2-
import type { UppyFile, SuccessResponse } from '@uppy/core'
32
import { Dashboard } from '@uppy/react';
43
import AwsS3 from '@uppy/aws-s3';
54
import { useEffect, useState } from 'react';
@@ -45,7 +44,7 @@ const extractSha256FromURI = (uri: string) => {
4544
}
4645

4746
const assetInspectorFormSchema = z.object({
48-
uris: z.string(),
47+
uris: z.string(),
4948
});
5049

5150
async function resolveURI(uri: string) {
@@ -115,7 +114,7 @@ export function AssetInspector() {
115114
const uris = urisString.split('\n').map((uri) => uri.trim()).filter((uri) => uri.length > 0);
116115

117116
try {
118-
const results = await Promise.all(uris.map(uri =>
117+
const results = await Promise.all(uris.map(uri =>
119118
resolveURI(uri).catch((error: Error) => ({ uri: uri, error: error }))
120119
));
121120

@@ -158,30 +157,30 @@ export function AssetInspector() {
158157
<div key={res.uri}>
159158
{i !== 0 && (<Separator className="my-6" />)}
160159
<span className="text-sm text-gray-500">URI</span>
161-
<Pre hasCopyCode className="-mt-5"><Code>{res.uri}</Code></Pre>
160+
<Pre className="-mt-5"><Code>{res.uri}</Code></Pre>
162161
{'result' in res && (
163162
<>
164163
<span className="text-sm text-gray-500">Resolved</span>
165-
<Pre hasCopyCode className="-mt-5"><Code>{res.result.url}</Code></Pre>
164+
<Pre className="-mt-5"><Code>{res.result.url}</Code></Pre>
166165
{res.result.lastModified && (
167166
<>
168167
<span className="text-sm text-gray-500 block">Last Modified</span>
169-
<Pre hasCopyCode className="-mt-5"><Code>{dayjs().to(res.result.lastModified)} ({res.result.lastModified.toISOString()})</Code></Pre>
168+
<Pre className="-mt-5"><Code>{dayjs().to(res.result.lastModified)} ({res.result.lastModified.toISOString()})</Code></Pre>
170169
</>
171170

172171
)}
173172
<>
174173
<span className="text-sm text-gray-500 block">Expires</span>
175-
<Pre hasCopyCode className="-mt-5"><Code>{
176-
res.result.expiresAt ?
174+
<Pre className="-mt-5"><Code>{
175+
res.result.expiresAt ?
177176
`${dayjs().to(res.result.expiresAt)} (${res.result.expiresAt.toISOString()})` :
178177
'Never'
179178
}</Code></Pre>
180179
</>
181180
{res.result.headers && (
182181
<>
183182
<span className="text-sm text-gray-500 block">Raw Headers</span>
184-
<Pre hasCopyCode className="-mt-5"><Code>{JSON.stringify(Object.fromEntries(res.result.headers), null, 2)}</Code></Pre>
183+
<Pre className="-mt-5"><Code>{JSON.stringify(Object.fromEntries(res.result.headers), null, 2)}</Code></Pre>
185184
</>
186185
)}
187186
</>
@@ -225,38 +224,39 @@ export function AssetUploader() {
225224
maxFileSize: UPLOADER_MAX_FILE_SIZE,
226225
},
227226
})
228-
.use(AwsS3, {
229-
shouldUseMultipart: false,
230-
getUploadParameters: async (file) => {
231-
const hash = sha256(await file.data.arrayBuffer());
232-
sha256Cache.set(file.id, hash);
233-
234-
return {
235-
method: 'PUT',
236-
url: `${UPLOADER_S3_HOST}/${UPLOADER_S3_BUCKET}/${hash}`,
237-
};
238-
}
239-
}));
227+
.use(AwsS3, {
228+
shouldUseMultipart: false,
229+
getUploadParameters: async (file) => {
230+
const hash = sha256(await file.data.arrayBuffer());
231+
sha256Cache.set(file.id, hash);
232+
233+
return {
234+
method: 'PUT',
235+
url: `${UPLOADER_S3_HOST}/${UPLOADER_S3_BUCKET}/${hash}`,
236+
};
237+
}
238+
}));
240239

241240
useEffect(() => {
242241
function handleUpload() {
243242
setErrorMessages([]);
244243
}
245-
async function handleUploadSuccess(file: UppyFile | undefined, response: SuccessResponse) {
244+
async function handleUploadSuccess(file: { id: string; name?: string; data: Blob } | undefined, response: unknown) {
246245
if (!file) {
247246
console.warn('Got upload success event without a file:', response)
248247
return;
249248
}
250249
const hash = sha256Cache.get(file.id) || sha256(await file.data.arrayBuffer());
251-
const watcloudURI = `watcloud://v1/sha256:${hash}?name=${encodeURIComponent(file.name)}`;
250+
const fileName = file.name ?? 'unnamed';
251+
const watcloudURI = `watcloud://v1/sha256:${hash}?name=${encodeURIComponent(fileName)}`;
252252
console.log('Uploaded file:', file, 'Response:', response, 'watcloud URI:', watcloudURI);
253253

254254
setSuccessfulUploads((prev) => [{
255-
name: file.name,
255+
name: fileName,
256256
uri: watcloudURI,
257257
}, ...prev]);
258258
}
259-
function handleUppyError(file: UppyFile | undefined, error: any) {
259+
function handleUppyError(file: { id: string; name?: string } | undefined, error: any) {
260260
console.error('Failed upload:', file, "Error:", error, "Response status:", error.source?.status);
261261
setErrorMessages((prev) => [`Failed to upload ${file?.name}: "${error.message}", response status: "${error.source?.status}", response body: "${error.source?.responseText}"`, ...prev]);
262262
}
@@ -274,33 +274,33 @@ export function AssetUploader() {
274274

275275
return (
276276
<>
277-
<Dashboard
278-
uppy={uppy}
279-
note={`Maximum file size: ${bytesToSize(UPLOADER_MAX_FILE_SIZE, 0)}`}
280-
width="100%"
281-
theme={uppyTheme}
282-
showProgressDetails={true}
283-
/>
284-
<h4 className="mt-8 mb-4 text-md">Successful Uploads</h4>
285-
{successfulUploads.map(({name, uri}) => (
286-
<div key={uri}>
287-
<span className="text-sm text-gray-500">{name}</span>
288-
<Pre hasCopyCode className="-mt-5"><Code>{uri}</Code></Pre>
289-
</div>
290-
))}
291-
{successfulUploads.length === 0 && (
292-
<p className="text-sm text-gray-500">No successful uploads yet. Upload a file to get started!</p>
293-
)}
294-
{errorMessages.length > 0 && (
295-
<>
296-
<h4 className="mt-8 mb-4 text-md">Errors</h4>
297-
{errorMessages.map((message, i) => (
298-
<div key={i}>
299-
<span className="text-red-500">{message}</span>
300-
</div>
301-
))}
302-
</>
303-
)}
277+
<Dashboard
278+
uppy={uppy}
279+
note={`Maximum file size: ${bytesToSize(UPLOADER_MAX_FILE_SIZE, 0)}`}
280+
width="100%"
281+
theme={uppyTheme}
282+
showProgressDetails={true}
283+
/>
284+
<h4 className="mt-8 mb-4 text-md">Successful Uploads</h4>
285+
{successfulUploads.map(({ name, uri }) => (
286+
<div key={uri}>
287+
<span className="text-sm text-gray-500">{name}</span>
288+
<Pre className="-mt-5"><Code>{uri}</Code></Pre>
289+
</div>
290+
))}
291+
{successfulUploads.length === 0 && (
292+
<p className="text-sm text-gray-500">No successful uploads yet. Upload a file to get started!</p>
293+
)}
294+
{errorMessages.length > 0 && (
295+
<>
296+
<h4 className="mt-8 mb-4 text-md">Errors</h4>
297+
{errorMessages.map((message, i) => (
298+
<div key={i}>
299+
<span className="text-red-500">{message}</span>
300+
</div>
301+
))}
302+
</>
303+
)}
304304
</>
305305
);
306306
}

components/github.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { useState } from "react";
1414
import { useForm } from 'react-hook-form';
1515
import { z } from 'zod';
1616
import { Input } from "./ui/input";
17-
import { useMDXComponents } from "nextra-theme-docs";
17+
1818
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./ui/card";
1919
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
2020
import { Link } from "nextra-theme-docs"
@@ -121,9 +121,9 @@ export function UsernameToID() {
121121

122122
// Use MDX components from the theme:
123123
// https://github.com/shuding/nextra/blob/33a2f9a5fe8eb58f78b4b9c8a671bc7f854ea504/packages/nextra-theme-docs/src/mdx-components.tsx#L113
124-
const components = useMDXComponents();
125-
const Summary = components.summary ?? "summary";
126-
const Details = components.details ?? "details";
124+
// const components = useMDXComponents();
125+
const Summary = "summary";
126+
const Details = "details";
127127

128128
const form = useForm<z.infer<typeof usernameToIDFormSchema>>({
129129
resolver: zodResolver(usernameToIDFormSchema),
@@ -217,7 +217,7 @@ export function UsernameToID() {
217217
{globalNodeID && (
218218
<div className="mt-8">
219219
<h2 className="text-xl font-bold">Global Node ID</h2>
220-
<Pre hasCopyCode><Code>{globalNodeID}</Code></Pre>
220+
<Pre><Code>{globalNodeID}</Code></Pre>
221221
</div>
222222
)}
223223
{rawData && (
@@ -228,7 +228,7 @@ export function UsernameToID() {
228228
<Details>
229229
<Summary>Raw data</Summary>
230230
<div className="mt-2">
231-
<Pre hasCopyCode><Code>{JSON.stringify(rawData, null, 2)}</Code></Pre>
231+
<Pre><Code>{JSON.stringify(rawData, null, 2)}</Code></Pre>
232232
</div>
233233
</Details>
234234
</div>

0 commit comments

Comments
 (0)