Skip to content

Commit

Permalink
Create home page
Browse files Browse the repository at this point in the history
  • Loading branch information
henrycatalinismith committed May 26, 2024
1 parent 4ab77cb commit 3d96ce2
Show file tree
Hide file tree
Showing 3 changed files with 275 additions and 0 deletions.
57 changes: 57 additions & 0 deletions webapp/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Cache } from '@/Cache';
import HomeDashboard from '@/components/HomeDashboard';
import MessageAdapterFactory from '@/utils/adapters/MessageAdapterFactory';
import { ProjectCardProps } from '@/components/ProjectCard';
import { RepoGit } from '@/RepoGit';
import { ServerConfig } from '@/utils/serverConfig';

// Force dynamic rendering for this page. By default Next.js attempts to render
// this page statically. That means that it tries to render the page at build
// time instead of at runtime. That doesn't work: this page needs to fetch
// project-specific config files and perform git operations. So this little
// one-liner forces it into dynamic rendering mode.
//
// More info on dynamic vs static rendering at:
// https://nextjs.org/learn/dashboard-app/static-and-dynamic-rendering
//
// More info on `export const dynamic` at:
// https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config
export const dynamic = 'force-dynamic';

export default async function Home() {
const serverConfig = await ServerConfig.read();
const projects = await Promise.all(
serverConfig.projects.map<Promise<ProjectCardProps>>(async (project) => {
await RepoGit.cloneIfNotExist(project);
const repoGit = await RepoGit.getRepoGit(project);
const lyraConfig = await repoGit.getLyraConfig();
const projectConfig = lyraConfig.getProjectConfigByPath(
project.projectPath,
);
const msgAdapter = MessageAdapterFactory.createAdapter(projectConfig);
const messages = await msgAdapter.getMessages();
const store = await Cache.getProjectStore(projectConfig);
const languages = await Promise.all(
projectConfig.languages.map(async (lang) => {
const translations = await store.getTranslations(lang);
return {
href: `/projects/${project.name}/${lang}`,
language: lang,
progress: translations
? (Object.keys(translations).length / messages.length) * 100
: 0,
};
}),
);

return {
href: `/projects/${project.name}`,
languages,
messageCount: messages.length,
name: project.name,
};
}),
);

return <HomeDashboard projects={projects} />;
}
46 changes: 46 additions & 0 deletions webapp/src/components/HomeDashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { FC } from 'react';
import { Box, List, Typography } from '@mui/joy';
import ProjectCard, { ProjectCardProps } from './ProjectCard';

type HomeDashboardProps = {
projects: ProjectCardProps[];
};

const HomeDashboard: FC<HomeDashboardProps> = ({ projects }) => {
return (
<Box
alignItems="center"
display="flex"
flexDirection="column"
justifyContent="center"
minHeight="97vh"
>
<Typography alignSelf="flex-start" color="primary" component="h1">
Your Lyra Projects
</Typography>
<List
sx={{
'@media (min-width: 600px)': {
alignContent: 'center',
columnGap: 2,
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 300px));',
justifyContent: 'center',
},
alignItems: 'center',
display: 'flex',
flex: 1,
flexDirection: 'column',
rowGap: 2,
width: '100%',
}}
>
{projects.map((project, i) => (
<ProjectCard key={i} {...project} />
))}
</List>
</Box>
);
};

export default HomeDashboard;
172 changes: 172 additions & 0 deletions webapp/src/components/ProjectCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { FC } from 'react';
import { Box, LinearProgress, Link, Typography } from '@mui/joy';

export type ProjectCardProps = {
/**
* The URL of the project page.
*/
href: string;

/**
* The project's languages and their translation progress.
*/
languages: {
/**
* The URL of the page containing the project's messages in this language.
*/

href: string;

/**
* The name of the language.
*/
language: string;

/**
* The percentage of messages translated in this language. 0 means none, 100
* means all of them.
*/
progress: number;
}[];

/**
* The number of messages in the project.
*/
messageCount: number;

/**
* The name of the project.
*/
name: string;
};

/**
* Project cards are the primary navigation element on the home screen. They
* display information about the project, and clicking them takes the user to
* the project page.
*
* Displaying a collection of structured information like this in a clickable
* card introduces some accessibility challenges. The implementation here
* employs the [Inclusive Components "pseudo-content trick"](https://inclusive-components.design/cards/).
*
*/
const ProjectCard: FC<ProjectCardProps> = ({
href,
languages,
name,
messageCount,
}) => {
return (
<Box component="li" sx={{ listStyleType: 'none' }} width="100%">
<Box
bgcolor="neutral.50"
border={1}
borderColor="transparent"
borderRadius={8}
display="flex"
flexDirection="column"
position="relative"
px={3}
py={2}
rowGap={1}
sx={{
':focus-within, :hover': {
outlineColor: 'focusVisible',
outlineStyle: 'solid',
outlineWidth: 1,
},
}}
>
<Typography component="h2">
<Link
href={href}
sx={{
'::after': {
bottom: 0,
content: '""',
left: 0,
position: 'absolute',
right: 0,
top: 0,
width: '100%',
},
':hover, :focus': {
outline: 'none',
textDecoration: 'none',
},
color: 'inherit',
position: 'inherit',
}}
>
{name}
</Link>
</Typography>
<Typography>{messageCount} messages</Typography>
<Box
columnGap={1}
component="ul"
display="grid"
margin={0}
padding={0}
rowGap={1}
sx={{
gridTemplateColumns: 'repeat(auto-fit, minmax(30px, 120px));',
width: '100%',
}}
>
{languages.map(({ href, language, progress }) => (
<Box
key={language}
bgcolor="primary.50"
borderRadius={4}
component="li"
position="relative"
sx={{
':focus-within, :hover': {
outlineColor: 'focusVisible',
outlineStyle: 'solid',
outlineWidth: 1,
},
listStyleType: 'none',
}}
>
<Box display="flex" flexDirection="column" px={3} py={2}>
<Link
href={href}
sx={{
'::after': {
bottom: 0,
content: '""',
left: 0,
position: 'absolute',
right: 0,
top: 0,
width: '100%',
},
':hover, :focus': {
outline: 'none',
textDecoration: 'none',
},
position: 'inherit',
}}
>
{language}
</Link>
<LinearProgress
determinate
size="lg"
sx={{ backgroundColor: '#ffffff' }}
thickness={8}
value={Math.min(progress, 100)}
variant="outlined"
/>
</Box>
</Box>
))}
</Box>
</Box>
</Box>
);
};

export default ProjectCard;

0 comments on commit 3d96ce2

Please sign in to comment.