diff --git a/apps/web/components/header/tab-navigation.tsx b/apps/web/components/header/tab-navigation.tsx
index 319d6090e..034d3e488 100644
--- a/apps/web/components/header/tab-navigation.tsx
+++ b/apps/web/components/header/tab-navigation.tsx
@@ -81,7 +81,7 @@ export default function TabNavigation() {
key={href}
href={href}
aria-current={href === pathname && "page"}
- className="aria-current-page:border-b-2 min-w-tab-link border-primary-light dark:border-primary-elevated-light flex-shrink-0 whitespace-nowrap p-4 text-sm uppercase hover:opacity-80"
+ className="aria-current-page:border-b-2 border-primary-light dark:border-primary-elevated-light min-w-[90px] flex-shrink-0 whitespace-nowrap p-4 text-sm uppercase hover:opacity-80"
>
{title}
diff --git a/apps/web/components/page-title.tsx b/apps/web/components/page-title.tsx
new file mode 100644
index 000000000..df7657168
--- /dev/null
+++ b/apps/web/components/page-title.tsx
@@ -0,0 +1,12 @@
+type PageTitleProps = React.PropsWithChildren<{
+ Icon?: React.ElementType;
+}>;
+
+export default function PageTitle({ children, Icon }: PageTitleProps) {
+ return (
+
+ {Icon && }
+
{children}
+
+ );
+}
diff --git a/apps/web/lib/constants.ts b/apps/web/lib/constants.ts
new file mode 100644
index 000000000..2b116e56f
--- /dev/null
+++ b/apps/web/lib/constants.ts
@@ -0,0 +1 @@
+export const ERROR_404_TITLE = "404 | Sidan hittades inte | Partiguiden";
diff --git a/apps/web/lib/navigation.ts b/apps/web/lib/navigation.ts
index 04f413b50..947eee4c2 100644
--- a/apps/web/lib/navigation.ts
+++ b/apps/web/lib/navigation.ts
@@ -12,13 +12,15 @@ import {
export const routes = {
index: "/",
cookiePolicy: "/cookie-policy",
- aboutUs: "/about-us",
+ aboutUs: "/om-oss",
polls: "/polls",
- votes: "/vote",
+ votes: "/voteringar",
vote: "/vote/[id]/[bet]",
decisions: "/decisions",
- standpoints: "/standpoints",
- standpoint: "/standpoints/[id]",
+ standpoints: "/standpunkter",
+ standpoint(id: string) {
+ return `/standpunkter/${id}`;
+ },
party: "/party/[party]",
members: "/member",
member: "/member/[id]",
@@ -29,19 +31,6 @@ export const routes = {
debate: "/debate/[id]",
};
-export const getVoteHref = (id: string, bet: number): string =>
- `/vote/${id}/${bet}`;
-
-export const getStandpointHref = (id: number): string => `/standpoints/${id}`;
-
-export const getPartyHref = (party: string): string => `/party/${party}`;
-
-export const getMemberHref = (id: string): string => `/member/${id}`;
-
-export const getDocumentHref = (id: string): string => `/document/${id}`;
-
-export const getDebateHref = (id: string): string => `/debate/${id}`;
-
export const mainNavigation = [
{ href: routes.index, title: "Hem", Icon: HomeIcon },
{
diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs
index 56ad48d01..2aeb01aee 100644
--- a/apps/web/next.config.mjs
+++ b/apps/web/next.config.mjs
@@ -7,6 +7,7 @@ const withBundleAnalyzer = bundleAnalyzer({
let moduleExports = withBundleAnalyzer({
productionBrowserSourceMaps: true,
+ transpilePackages: ["@partiguiden/party-data"],
basePath: "",
images: {
remotePatterns: [
diff --git a/apps/web/package.json b/apps/web/package.json
index 4b763ba91..ec3b25c95 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -26,6 +26,7 @@
"@mui/material": "5.14.9",
"@mui/system": "5.14.9",
"@next/bundle-analyzer": "13.4.19",
+ "@partiguiden/party-data": "workspace:*",
"@sentry/nextjs": "7.69.0",
"isomorphic-unfetch": "4.0.2",
"jsdom": "22.1.0",
diff --git a/apps/web/src/containers/Subjects.tsx b/apps/web/src/containers/Subjects.tsx
deleted file mode 100644
index c44f324db..000000000
--- a/apps/web/src/containers/Subjects.tsx
+++ /dev/null
@@ -1,159 +0,0 @@
-import Link from "next/link";
-import React, { useState } from "react";
-
-import { styled } from "@mui/material/styles";
-import Grid from "@mui/material/Grid";
-
-import type { SubjectList } from "../types/subjects";
-
-import * as ROUTES from "../lib/routes";
-import { ResponsiveAd } from "../components/Ad";
-import Search from "../components/Search";
-
-const SearchContainer = styled("div")`
- width: 100%;
- margin-top: -1rem;
-`;
-
-const Transition = styled("span")`
- margin: 0;
- background: linear-gradient(
- to left,
- transparent 50%,
- ${({ theme }) =>
- theme.palette.mode === "dark"
- ? theme.palette.primary.dark
- : theme.palette.primary.main}
- 50%
- );
- background-size: 202% 100%;
- background-position: right bottom;
- background-repeat: no-repeat;
- color: ${({ theme }) =>
- theme.palette.mode === "dark"
- ? theme.palette.primary.contrastText
- : theme.palette.grey[900]};
- line-height: 50px;
- padding: 0 0.5rem;
- transition: all 0.2s ease-in-out;
-`;
-
-const Button = styled("a")`
- text-decoration: none;
- display: flex;
- flex: 1;
- font-size: 1rem;
- justify-content: flex-start;
- :hover span {
- background-position: left bottom;
- color: ${({ theme }) => theme.palette.grey[100]};
- }
-`;
-
-const Item = styled(Grid)(
- ({ theme }) => `
- ${theme.breakpoints.down("sm")} {
- border-left: solid 2px ${
- theme.palette.mode === "dark"
- ? theme.palette.primary.dark
- : theme.palette.primary.main
- };
- }
- ${theme.breakpoints.up("md")} {
- :nth-child(2n + 1) {
- border-left: solid 2px ${
- theme.palette.mode === "dark"
- ? theme.palette.primary.dark
- : theme.palette.primary.main
- };
- }
- :nth-child(2n) {
- border-right: solid 2px ${
- theme.palette.mode === "dark"
- ? theme.palette.primary.dark
- : theme.palette.primary.main
- };
- }
- }
- :nth-child(3n) {
- background-color:
- ${
- theme.palette.mode === "dark"
- ? theme.palette.background.paper
- : theme.palette.grey[50]
- };
- }
- :nth-child(3n + 1) {
- background-color:
- ${
- theme.palette.mode === "dark"
- ? theme.palette.background.paper
- : theme.palette.grey[100]
- };
- }
- :nth-child(3n + 2) {
- background-color:
- ${
- theme.palette.mode === "dark"
- ? theme.palette.background.paper
- : theme.palette.grey[200]
- };
- }
- `,
-);
-
-const Container = styled("div")(
- ({ theme }) => `
- margin-left: auto;
- margin-right: auto;
- ${theme.breakpoints.down("md")} {
- width: 100%;
- }
- ${theme.breakpoints.up("md")} {
- max-width: 70%;
- }
- ${theme.breakpoints.up("lg")} {
- max-width: 60%;
- }
-`,
-);
-
-interface Props {
- subjects: SubjectList;
-}
-
-const Subjects: React.FC
= ({ subjects }) => {
- const [shownSubjects, setShownSubjects] = useState(subjects);
-
- return (
-
-
-
-
-
- {shownSubjects.map((subject) => (
- -
-
-
-
-
- ))}
-
-
-
- );
-};
-
-export default Subjects;
diff --git a/apps/web/src/pages/standpoints/index.tsx b/apps/web/src/pages/standpoints/index.tsx
deleted file mode 100644
index 57a9c8bfd..000000000
--- a/apps/web/src/pages/standpoints/index.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import type { GetStaticProps, InferGetStaticPropsType, NextPage } from "next";
-import Head from "next/head";
-
-import NoteIcon from "@mui/icons-material/Note";
-
-import PageTitle from "../../components/PageTitle";
-
-import type { SubjectList } from "../../types/subjects";
-import { getSubjects } from "../../lib/api";
-import Subjects from "../../containers/Subjects";
-
-const StandpointsListContainer: NextPage<
- InferGetStaticPropsType
-> = ({ subjects }) => (
- <>
-
- Partiernas ståndpunkter | Partiguiden
-
-
-
-
- >
-);
-
-export const getStaticProps: GetStaticProps<{
- subjects: SubjectList;
-}> = async () => {
- const subjects = await getSubjects();
-
- return { props: { subjects }, revalidate: 518400 };
-};
-
-export default StandpointsListContainer;
diff --git a/apps/web/tailwind.config.ts b/apps/web/tailwind.config.ts
index e6b058421..53e32a88c 100644
--- a/apps/web/tailwind.config.ts
+++ b/apps/web/tailwind.config.ts
@@ -51,7 +51,6 @@ const config: Config = {
},
minWidth: {
screen: "100vw",
- "tab-link": "90px",
},
minHeight: {
screen: [
diff --git a/packages/party-data/cli.ts b/packages/party-data/cli.ts
index d1be79f71..5d650a490 100644
--- a/packages/party-data/cli.ts
+++ b/packages/party-data/cli.ts
@@ -1,10 +1,7 @@
import { Separator, select } from "@inquirer/prompts";
-import {
- readNotCategorizedStandpoints,
- readSubjects,
- updateStandpoint,
-} from ".";
-import type { Standpoint } from "./client";
+import { readSubjects, readNotCategorizedStandpoints } from "./reader";
+import { updateStandpoint } from "./writer";
+import type { Standpoint } from "./types";
const promptTemplate = (
standpoint: Standpoint,
diff --git a/packages/party-data/package.json b/packages/party-data/package.json
index f38b75289..4052a1129 100644
--- a/packages/party-data/package.json
+++ b/packages/party-data/package.json
@@ -10,7 +10,10 @@
},
"exports": {
".": "./index.ts",
- "./client": "./client.ts"
+ "./utils": "./utils.ts",
+ "./reader": "./reader.ts",
+ "./writer": "./writer.ts",
+ "./types": "./types.ts"
},
"devDependencies": {
"@partiguiden/eslint-config-base": "workspace:*",
diff --git a/packages/party-data/client.ts b/packages/party-data/reader.ts
similarity index 58%
rename from packages/party-data/client.ts
rename to packages/party-data/reader.ts
index e9f0231f3..8c8fb7cbb 100644
--- a/packages/party-data/client.ts
+++ b/packages/party-data/reader.ts
@@ -7,37 +7,17 @@ import kd from "./parties/kd.json";
import c from "./parties/c.json";
import v from "./parties/v.json";
import subjects from "./subjects.json";
-
-enum Party {
- S = "s",
- SD = "sd",
- M = "m",
- MP = "mp",
- L = "l",
- KD = "kd",
- C = "c",
- V = "v",
-}
-
-export interface Subject {
- id: string;
- name: string;
- relatedSubjects: string[];
-}
-
-export interface Standpoint {
- title: string;
- url: string;
- opinions: string[];
- fetchDate: string;
- party: string;
- subject?: string;
-}
+import type { Standpoint } from "./types";
+import { Party, type Subject } from "./types";
export function getSubjects(): Subject[] {
return Object.values(subjects);
}
+export function getSubject(id: string): Subject | undefined {
+ return getSubjects().find((subject) => subject.id === id);
+}
+
function getPartyData(abbreviation: string) {
switch (abbreviation.toLocaleLowerCase()) {
case Party.S:
@@ -61,7 +41,21 @@ function getPartyData(abbreviation: string) {
}
export function readPartyStandpoints(abbreviation: Party): Standpoint[] {
- return Object.values(getPartyData(abbreviation) as unknown);
+ return Object.values(getPartyData(abbreviation));
+}
+
+export function getStandpointsForSubject(subject: string) {
+ return Object.values(Party)
+ .sort()
+ .reduce>(
+ (prev, party) => ({
+ ...prev,
+ [party]: readPartyStandpoints(party).filter(
+ (standpoint) => standpoint.subject === subject,
+ ),
+ }),
+ {} as Record,
+ );
}
export function readPartyDataForSubject(party: Party, subjectName: string) {
@@ -72,3 +66,12 @@ export function readPartyDataForSubject(party: Party, subjectName: string) {
export function readAllStandpoints(): Standpoint[] {
return Object.values(Party).map(readPartyStandpoints).flat();
}
+
+export function readNotCategorizedStandpoints() {
+ const standpoints = readAllStandpoints();
+ return standpoints.filter((standpoint) => standpoint.subject === undefined);
+}
+
+export function readSubjects(): Subject[] {
+ return Object.values(subjects);
+}
diff --git a/packages/party-data/types.ts b/packages/party-data/types.ts
new file mode 100644
index 000000000..db824cc87
--- /dev/null
+++ b/packages/party-data/types.ts
@@ -0,0 +1,35 @@
+export enum Party {
+ S = "s",
+ SD = "sd",
+ M = "m",
+ MP = "mp",
+ L = "l",
+ KD = "kd",
+ C = "c",
+ V = "v",
+}
+
+export interface Subject {
+ id: string;
+ name: string;
+ relatedSubjects: string[];
+}
+
+export interface Standpoint {
+ title: string;
+ url: string;
+ opinions: string[];
+ fetchDate: string;
+ party: string;
+ subject?: string;
+}
+
+export interface SubjectData {
+ [id: string]: Subject;
+}
+
+export interface PartyData {
+ [url: string]: Standpoint;
+}
+
+export type PartyDataWithoutPartyName = Omit;
diff --git a/packages/party-data/utils.ts b/packages/party-data/utils.ts
new file mode 100644
index 000000000..7afae762f
--- /dev/null
+++ b/packages/party-data/utils.ts
@@ -0,0 +1,22 @@
+import { Party } from "./types";
+
+export function getPartyName(party: Party): string {
+ switch (party) {
+ case Party.C:
+ return "Centerpartiet";
+ case Party.KD:
+ return "Kristdemokraterna";
+ case Party.L:
+ return "Liberalerna";
+ case Party.M:
+ return "Moderaterna";
+ case Party.MP:
+ return "Miljöpartiet";
+ case Party.S:
+ return "Socialdemokraterna";
+ case Party.SD:
+ return "Sverigedemokraterna";
+ case Party.V:
+ return "Vänsterpartiet";
+ }
+}
diff --git a/packages/party-data/index.ts b/packages/party-data/writer.ts
similarity index 67%
rename from packages/party-data/index.ts
rename to packages/party-data/writer.ts
index 861ee088f..88bbafa3c 100644
--- a/packages/party-data/index.ts
+++ b/packages/party-data/writer.ts
@@ -1,18 +1,7 @@
import * as fs from "node:fs";
-import { readAllStandpoints, type Standpoint, type Subject } from "./client";
-
-export interface PartyData {
- [url: string]: Standpoint;
-}
-
-export type PartyDataWithoutPartyName = Omit;
-
-interface SubjectData {
- [id: string]: Subject;
-}
+import type { PartyData, PartyDataWithoutPartyName, Standpoint } from "./types";
const PARTIES_DIRECTORY = `${__dirname}/parties`;
-const SUBJECTS_FILE = `${__dirname}/subjects.json`;
const partyFileName = (abbreviation: string) =>
`${PARTIES_DIRECTORY}/${abbreviation.toLocaleLowerCase()}.json`;
@@ -85,28 +74,3 @@ export function updateStandpoint(abbreviation: string, standpoint: Standpoint) {
storedData[standpoint.url] = standpoint;
fs.writeFileSync(fileName, JSON.stringify(storedData, null, 2) + "\n");
}
-
-function readSubjectData() {
- return JSON.parse(fs.readFileSync(SUBJECTS_FILE).toString()) as SubjectData;
-}
-
-export function readSubjects(): Subject[] {
- return Object.values(readSubjectData());
-}
-
-export function readPartyData(abbreviation: string): Standpoint[] {
- const partyData = JSON.parse(
- fs.readFileSync(partyFileName(abbreviation)).toString(),
- ) as PartyData;
- return Object.values(partyData);
-}
-
-export function readPartyDataForSubject(party: string, subjectName: string) {
- const partyData = readPartyData(party);
- return partyData.filter((subject) => subject.subject === subjectName);
-}
-
-export function readNotCategorizedStandpoints() {
- const standpoints = readAllStandpoints();
- return standpoints.filter((standpoint) => standpoint.subject === undefined);
-}
diff --git a/packages/scrapers/src/index.ts b/packages/scrapers/src/index.ts
index 42d5bd914..137f38ba4 100644
--- a/packages/scrapers/src/index.ts
+++ b/packages/scrapers/src/index.ts
@@ -1,6 +1,6 @@
import { parseArgs } from "node:util";
import scrapers from "./scrapers";
-import { writePartyData } from "@partiguiden/party-data";
+import { writePartyData } from "@partiguiden/party-data/writer";
const {
values: { party },
diff --git a/packages/scrapers/src/party/sd-scraper.ts b/packages/scrapers/src/party/sd-scraper.ts
index abfd73c43..d0c4d0254 100644
--- a/packages/scrapers/src/party/sd-scraper.ts
+++ b/packages/scrapers/src/party/sd-scraper.ts
@@ -1,6 +1,6 @@
import * as pdfjs from "pdfjs-dist";
import Scraper from "../scraper";
-import type { PartyDataWithoutPartyName } from "@partiguiden/party-data";
+import type { PartyDataWithoutPartyName } from "@partiguiden/party-data/writer";
type SectionDestination = [
{ num: number; gen: number },
diff --git a/packages/scrapers/src/scraper.ts b/packages/scrapers/src/scraper.ts
index d40665eb3..ea704afe0 100644
--- a/packages/scrapers/src/scraper.ts
+++ b/packages/scrapers/src/scraper.ts
@@ -2,7 +2,7 @@ import type { Cheerio, CheerioAPI, Element } from "cheerio";
import type {
PartyData,
PartyDataWithoutPartyName,
-} from "@partiguiden/party-data";
+} from "@partiguiden/party-data/writer";
import * as cheerio from "cheerio";
interface ScraperArgs {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 08962d9f7..9938722c2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -45,6 +45,9 @@ importers:
'@next/bundle-analyzer':
specifier: 13.4.19
version: 13.4.19
+ '@partiguiden/party-data':
+ specifier: workspace:*
+ version: link:../../packages/party-data
'@sentry/nextjs':
specifier: 7.69.0
version: 7.69.0(next@13.4.19)(react@18.2.0)