Skip to content

[Aksel darkside] forsiden #3761

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
62 changes: 62 additions & 0 deletions aksel.nav.no/website/app/_sanity/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,66 @@ const GOD_PRAKSIS_ARTICLE_BY_SLUG_QUERY = defineQuery(
}`,
);

const GOD_PRAKSIS_TEMA_QUERY = defineQuery(
`*[_type == "gp.tema"] | order(lower(title))`,
);

const LANDINGSSIDE_LATEST_QUERY = defineQuery(`
*[_type == "aksel_forside"][0]{
blocks[]{
...,
_type == "nytt_fra_aksel"=>{
highlights[]->{
...,
"content": null,
${contributorsAll},
"tema": undertema[]->tema->title,
},
"curatedResent": {
"bloggposts": *[_type == "aksel_blogg" && !(_id in ^.highlights[]._ref)] | order(_createdAt desc)[0...4]{
_type,
_id,
heading,
_createdAt,
_updatedAt,
publishedAt,
"slug": slug.current,
ingress,
seo,
${contributorsAll}
},
"artikler": *[_type == "aksel_artikkel" && defined(publishedAt) && !(_id in ^.highlights[]._ref)] | order(publishedAt desc)[0...8]{
_type,
_id,
heading,
_createdAt,
_updatedAt,
publishedAt,
"slug": slug.current,
"tema": undertema[]->tema->title,
ingress,
seo,
${contributorsAll}
},
"komponenter": *[_type in ["komponent_artikkel", "ds_artikkel", "templates_artikkel"] && defined(publishedAt) && !(_id in ^.highlights[]._ref)] | order(publishedAt desc)[0...7]{
_type,
_id,
heading,
"slug": slug.current,
status,
kategori,
_createdAt,
_updatedAt,
publishedAt,
seo,
${contributorsAll}
},
},
}
}
}.blocks
`);

/* ---------------------------- Standalone pages ---------------------------- */

const SIDE_ARTICLE_BY_SLUG_QUERY = defineQuery(`
Expand Down Expand Up @@ -285,7 +345,9 @@ export {
GOD_PRAKSIS_TEMA_BY_SLUG_QUERY,
GOD_PRAKSIS_ARTICLES_BY_TEMA_QUERY,
GOD_PRAKSIS_ARTICLE_BY_SLUG_QUERY,
GOD_PRAKSIS_TEMA_QUERY,
DOCUMENT_BY_ID_FOR_SLACK_QUERY,
SIDE_ARTICLE_BY_SLUG_QUERY,
PRINSIPPER_BY_SLUG_QUERY,
LANDINGSSIDE_LATEST_QUERY,
};
59 changes: 59 additions & 0 deletions aksel.nav.no/website/app/_sanity/query-types.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
LinkCardIcon,
LinkCardTitle,
} from "@/app/dev/(god-praksis)/_ui/link-card/LinkCard";
import { GodPraksisPictogram } from "@/app/dev/(god-praksis)/_ui/pictogram/GodPraksisPictogram";
import { GodPraksisPictogram } from "@/app/dev/_ui/pictogram/GodPraksisPictogram";
import styles from "./Hero.module.css";

type GpIntroHeroProps = {
Expand Down
197 changes: 197 additions & 0 deletions aksel.nav.no/website/app/dev/_ui/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import cl from "clsx";
import NextImage from "next/legacy/image";
import NextLink from "next/link";
import {
BodyShort,
Detail,
HStack,
Heading,
Stack,
VStack,
} from "@navikt/ds-react";
import { urlForImage } from "@/app/_sanity/utils";
import { umamiTrack } from "@/app/_ui/umami/Umami.track";
import ErrorBoundary from "@/error-boundary";
import { useFormatedDate } from "@/hooks/useFormatedDate";
import { abbrName, getImage } from "@/utils";
import { BetaTag, Tag } from "./Tag";
import styles from "./landingpage.module.css";

export type ArticleT = {
_key: string;
_type: string;
_createdAt: string;
_updatedAt: string;
_id: string;
heading: string;
ingress?: string;
slug: { current: string };
seo?: {
meta?: string;
image?: any;
};
tema?: string[];
status?: { tag: string; bilde?: any };
publishedAt: string;
contributors?: { title: string }[];
};

type CardProps = {
article: ArticleT;
visible: boolean;
index: number;
};

const Card = ({ article, visible, index }: CardProps) => {
const date = useFormatedDate(article.publishedAt ?? article._updatedAt);

const showAuthor = ["aksel_artikkel", "aksel_blogg"].includes(article._type);

const showImage = [
"ds_artikkel",
"komponent_artikkel",
"aksel_blogg",
"templates_artikkel",
].includes(article._type);

const statusImageUrl = urlForImage(article.status?.bilde)?.url();
const statusImageBlurUrl = urlForImage(article.status?.bilde)
?.width(24)
.height(24)
.blur(10)
.url();

const fallbackImageUrl = urlForImage(article.seo?.image)?.url();
const fallbackImageBlurUrl = urlForImage(article.seo?.image)
?.width(24)
.height(24)
.blur(10)
.url();

let Image = (
<div className={styles.cardImageWrapper}>
<NextImage
layout="fill"
objectFit="cover"
src={getImage(article?.heading ?? "", "thumbnail")}
alt={article.heading + " thumbnail"}
aria-hidden
className={styles.cardImage}
/>
</div>
);

if (statusImageUrl) {
Image = (
<NextImage
src={statusImageUrl}
blurDataURL={statusImageBlurUrl}
placeholder="blur"
width="200"
height="200"
alt={article.heading + " thumbnail"}
aria-hidden
/>
);
} else if (fallbackImageUrl) {
Image = (
<div className={styles.cardImageWrapper}>
<NextImage
src={fallbackImageUrl}
blurDataURL={fallbackImageBlurUrl}
placeholder="blur"
layout="fill"
objectFit="cover"
alt={article.heading + " thumbnail"}
aria-hidden
className={styles.cardImage}
/>
</div>
);
}

return (
<div
className={cl(`${styles.card}`, {
[`${styles.cardVisible}`]: visible,
[`${styles.cardNotVisible}`]: !visible,
})}
style={{ transitionDelay: `${index * 70}ms` }}
>
{showImage && (
<div
className={cl(`${styles.cardImageWrapperWrapper}`, {
[`${styles.betaHue}`]: article?.status?.tag === "beta",
})}
>
{Image}
</div>
)}
<VStack
padding={{ xs: "space-12", sm: "space-20" }}
className={styles.cardContent}
>
<div>
<Stack direction="column-reverse">
<NextLink
href={`/${article.slug}`}
passHref
className={styles.cardLink}
onClick={() =>
umamiTrack("navigere", {
kilde: "forsidekort",
url: `/${article.slug}`,
})
}
>
<Heading
level="3"
size="small"
className={styles.cardLinkHeading}
>
{article.heading}
</Heading>
</NextLink>
</Stack>
{article.ingress ? (
<BodyShort className={styles.cardIngress}>
{article.ingress}
</BodyShort>
) : article.seo?.meta ? (
<BodyShort className={styles.cardIngress}>
{article.seo.meta}
</BodyShort>
) : null}
</div>

{showAuthor && (
<span className={styles.cardAuthor}>
{article?.contributors && (
<Detail as="span" weight="semibold">
{abbrName(article?.contributors[0]?.title)}
</Detail>
)}
<Detail as="span">{date}</Detail>
</span>
)}

<HStack gap="space-8">
<Tag
type={article._type}
text={article.tema ? article.tema[0] : undefined}
size="xsmall"
/>
{article.status?.tag === "beta" && <BetaTag />}
</HStack>
</VStack>
</div>
);
};

export default function Component(props: CardProps) {
return (
<ErrorBoundary boundaryName="FrontpageBlockCard">
<Card {...props} />
</ErrorBoundary>
);
}
114 changes: 114 additions & 0 deletions aksel.nav.no/website/app/dev/_ui/FrontpageLatest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"use client";

import cl from "clsx";
import { useEffect, useRef, useState } from "react";
import Masonry, { ResponsiveMasonry } from "react-responsive-masonry";
import { BoxNew, Heading } from "@navikt/ds-react";
import Card, { ArticleT } from "./Card";
import { Highlight } from "./Highlight";
import styles from "./landingpage.module.css";

export type LatestT = {
_type: "nytt_fra_aksel";
_key: string;
highlights: ArticleT[];
curatedResent: {
artikler: ArticleT[];
bloggposts: ArticleT[];
komponenter: ArticleT[];
};
};

type LatestArticlesProps = {
block: LatestT;
};

const Latest = ({ block }: LatestArticlesProps) => {
const highlights = block.highlights?.length;
const [intersected, setIntersected] = useState(false);
const section = useRef(null);

useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
entry.isIntersecting && setIntersected(entry.isIntersecting);
},
{ rootMargin: "0px 0px 100px 0px" },
);
section.current && observer.observe(section.current);
return () => observer.disconnect();
}, []);

const articles = getList(block);

return (
<>
<Heading level="2" size="xlarge" className={styles.latestHeading}>
Siste fra Aksel
</Heading>

{highlights && <Highlights highlights={block.highlights} />}
<section
ref={section}
aria-label="Nyeste artikler fra Aksel"
className={styles.latestSection}
>
<ResponsiveMasonry
columnsCountBreakPoints={{ 480: 1, 768: 2, 1024: 3 }}
>
<Masonry gutter="1.5rem">
{articles.map((x, index) => (
<Card
key={x._id}
article={x}
index={index}
visible={intersected}
/>
))}
</Masonry>
</ResponsiveMasonry>
</section>
</>
);
};

function Highlights({ highlights }: { highlights: ArticleT[] }) {
return (
<div
className={cl({
[`${styles.highlightWrapper}`]: highlights?.length === 2,
})}
>
{highlights.map((x, idx) => (
<Highlight article={x} key={idx} compact={highlights.length === 1} />
))}
</div>
);
}

function getList(block: LatestT) {
return [
...block.curatedResent.artikler,
...block.curatedResent.bloggposts,
...block.curatedResent.komponenter,
].sort((a, b) => {
return (
new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime()
);
});
}

export const FrontpageLatest = ({ latest }) => {
return (
<BoxNew paddingInline={{ xs: "space-8", lg: "space-72" }}>
{latest.map((x) => {
switch (x._type) {
case "nytt_fra_aksel":
return <Latest block={x} key={x._key} />;
default:
return null;
}
})}
</BoxNew>
);
};
Loading
Loading