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
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v20.13.1
51 changes: 51 additions & 0 deletions __test__/common/utils/makeFullRepositoryName.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { makeFullRepositoryName } from "@/common/utils/makeFullRepositoryName";

test("It returns a sanitized repository name with a suffix", async () => {
const result = makeFullRepositoryName({ name: "My Project", suffix: "-repo" });
expect(result).toEqual("myproject-repo");
});

test("It returns a lowercase repository name", async () => {
const result = makeFullRepositoryName({ name: "MyProject", suffix: "-repo" });
expect(result).toEqual("myproject-repo");
});

test("It trims spaces from the name", async () => {
const result = makeFullRepositoryName({ name: " My Project ", suffix: "-repo" });
expect(result).toEqual("myproject-repo");
});

test("It removes non-alphanumeric characters except dashes", async () => {
const result = makeFullRepositoryName({ name: "My!@#Pro$%^ject", suffix: "-repo" });
expect(result).toEqual("myproject-repo");
});

test("It replaces spaces with dashes", async () => {
const result = makeFullRepositoryName({ name: "My Project", suffix: "-repo" });
expect(result).toEqual("myproject-repo");
});

test("It handles names with multiple spaces correctly", async () => {
const result = makeFullRepositoryName({ name: "My Project", suffix: "-repo" });
expect(result).toEqual("myproject-repo");
});

test("It handles names with no characters left after sanitization", async () => {
const result = makeFullRepositoryName({ name: "!@#$%^", suffix: "-repo" });
expect(result).toEqual("-repo");
});

test("It handles names that are already safe", async () => {
const result = makeFullRepositoryName({ name: "safe-project-name", suffix: "-repo" });
expect(result).toEqual("safe-project-name-repo");
});

test("It returns just the suffix if the name is an empty string", async () => {
const result = makeFullRepositoryName({ name: "", suffix: "-repo" });
expect(result).toEqual("-repo");
});

test("It handles a suffix with special characters correctly", async () => {
const result = makeFullRepositoryName({ name: "My Project", suffix: "!-repo" });
expect(result).toEqual("myproject!-repo");
});
64 changes: 64 additions & 0 deletions __test__/common/utils/makeNewGitHubRepositoryLink.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { makeNewGitHubRepositoryLink } from "@/common/utils/makeNewGitHubRepositoryLink";

test("It generates a URL with repository name and description", async () => {
const result = makeNewGitHubRepositoryLink({
repositoryName: "test-repo",
description: "A test repository"
});
expect(result).toEqual(
"https://github.com/new?name=test-repo&description=A%20test%20repository&visibility=private"
);
});

test("It encodes special characters in the repository name and description", async () => {
const result = makeNewGitHubRepositoryLink({
repositoryName: "test repo",
description: "A test & description"
});
expect(result).toEqual(
"https://github.com/new?name=test%20repo&description=A%20test%20%26%20description&visibility=private"
);
});

test("It generates a URL with a template repository", async () => {
const result = makeNewGitHubRepositoryLink({
templateName: "owner/template-repo",
repositoryName: "test-repo",
description: "A test repository"
});
expect(result).toEqual(
"https://github.com/new?name=test-repo&description=A%20test%20repository&visibility=private&template_owner=owner&template_name=template-repo"
);
});

test("It handles template names with special characters", async () => {
const result = makeNewGitHubRepositoryLink({
templateName: "owner/template repo",
repositoryName: "test-repo",
description: "A test repository"
});
expect(result).toEqual(
"https://github.com/new?name=test-repo&description=A%20test%20repository&visibility=private&template_owner=owner&template_name=template%20repo"
);
});

test("It returns the URL without template parameters if templateName is not provided", async () => {
const result = makeNewGitHubRepositoryLink({
repositoryName: "test-repo",
description: "A test repository"
});
expect(result).toEqual(
"https://github.com/new?name=test-repo&description=A%20test%20repository&visibility=private"
);
});

test("It returns a URL even if the template name cannot be split into owner and repository", async () => {
const result = makeNewGitHubRepositoryLink({
templateName: "invalidTemplateName",
repositoryName: "test-repo",
description: "A test repository"
});
expect(result).toEqual(
"https://github.com/new?name=test-repo&description=A%20test%20repository&visibility=private"
);
});
2 changes: 1 addition & 1 deletion src/app/(authed)/(home)/[[...slug]]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default function Page({ children }: { children: React.ReactNode }) {
}
return (
<>
<SecondarySplitHeader mobileToolbar=<MobileToolbar/>>
<SecondarySplitHeader mobileToolbar={<MobileToolbar/>}>
<TrailingToolbarItem/>
</SecondarySplitHeader>
<main style={{ flexGrow: "1", overflowY: "auto" }}>
Expand Down
23 changes: 23 additions & 0 deletions src/app/(authed)/(home)/new/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"use client"

import SecondarySplitHeader from "@/features/sidebar/view/SecondarySplitHeader"
import { Box } from "@mui/material"

export default function Page({ children }: { children: React.ReactNode }) {
return (
<>
<Box width={1} display={{ xs: "flex", sm: "flex", md: "none"}}>
<SecondarySplitHeader showDivider={false} />
</Box>
<Box
display="flex"
alignItems="center"
justifyContent="center"
padding={{ xs: 4 }}
height={1}
>
{children}
</Box>
</>
)
}
59 changes: 10 additions & 49 deletions src/app/(authed)/(home)/new/page.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,17 @@
import Link from "next/link"
import { env, splitOwnerAndRepository } from "@/common"
import NewProject from "@/features/new-project/NewProject";

const Page = () => {
const repositoryNameSuffix = env.getOrThrow("REPOSITORY_NAME_SUFFIX")
const templateName = env.get("NEW_PROJECT_TEMPLATE_REPOSITORY")
const projectName = "Nordisk Film"
const suffixedRepositoryName = makeFullRepositoryName({
name: projectName,
suffix: repositoryNameSuffix
})
const newGitHubRepositoryLink = makeNewGitHubRepositoryLink({
templateName,
repositoryName: suffixedRepositoryName,
description: `Contains OpenAPI specifications for ${projectName}`
})
return (
<Link href={newGitHubRepositoryLink}>
{newGitHubRepositoryLink}
</Link>
)
}
const ownerRepository = templateName ? splitOwnerAndRepository(templateName)?.owner : undefined

export default Page

function makeFullRepositoryName({ name, suffix }: { name: string, suffix: string }) {
const safeRepositoryName = name
.trim()
.toLowerCase()
.replace(/[^a-z0-9-]+/g, "")
.replace(/\s+/g, "-")
return `${safeRepositoryName}${suffix}`
}
return (
<NewProject
repositoryNameSuffix={repositoryNameSuffix}
templateName={templateName}
ownerRepository={ownerRepository}
/>
)}

function makeNewGitHubRepositoryLink({
templateName,
repositoryName,
description
}: {
templateName?: string,
repositoryName: string,
description: string
}) {
let url = `https://github.com/new`
+ `?name=${encodeURIComponent(repositoryName)}`
+ `&description=${encodeURIComponent(description)}`
+ `&visibility=private`
if (templateName) {
const templateRepository = splitOwnerAndRepository(templateName)
if (templateRepository) {
url += `&template_owner=${encodeURIComponent(templateRepository.owner)}`
url += `&template_name=${encodeURIComponent(templateRepository.repository)}`
}
}
return url
}
export default Page
33 changes: 9 additions & 24 deletions src/app/auth/signin/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import Image from "next/image"
import Link from "next/link"
import { Box, Button, Stack, Typography } from "@mui/material"
import { signIn } from "@/composition"
import { env } from "@/common"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { faGithub } from "@fortawesome/free-brands-svg-icons"
import SignInTexts from "@/features/auth/view/SignInTexts"
import MessageLinkFooter from "@/common/ui/MessageLinkFooter"

const SITE_NAME = env.getOrThrow("NEXT_PUBLIC_SHAPE_DOCS_TITLE")
const HELP_URL = env.get("NEXT_PUBLIC_SHAPE_DOCS_HELP_URL")
Expand Down Expand Up @@ -74,9 +74,14 @@ const SignInColumn = () => {
<SignInWithGitHub />
</Stack>
</Box>
<Box sx={{ marginBottom: 2 }}>
<Footer/>
</Box>
{HELP_URL && (
<Box sx={{ marginBottom: 2 }}>
<MessageLinkFooter
url={HELP_URL}
content={`Learn more about ${SITE_NAME}`}
/>
</Box>
)}
</Box>
)
}
Expand All @@ -99,24 +104,4 @@ const SignInWithGitHub = () => {
</Button>
</form>
)
}

const Footer = () => {
return (
<Stack direction="row">
{HELP_URL &&
<Link href={HELP_URL} target="_blank" rel="noopener">
<Typography variant="body2" sx={{
opacity: 0.5,
transition: "opacity 0.3s ease",
"&:hover": {
opacity: 1
}
}}>
Learn more about {SITE_NAME}
</Typography>
</Link>
}
</Stack>
)
}
Loading