Skip to content
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

Feature/user frontend #99

Open
wants to merge 11 commits into
base: dev
Choose a base branch
from
Open
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
17 changes: 17 additions & 0 deletions src/app.tsx
Original file line number Diff line number Diff line change
@@ -10,9 +10,17 @@ import { NotFound } from "./pages/not-found";
import { Stories } from "./pages/stories";
import { Blog } from "./pages/blog";
import { Post } from "./pages/post";
import { SignUp } from "./pages/sign_up";
import { SignIn } from "./pages/sign_in";
import { Dashboard } from "./pages/Dashboard";
import { Edit } from "./pages/Edit";

import { Chapter1 } from "./pages/chapter1";
import { Chapter2 } from "./pages/chapter2";
import { Ticket } from "./pages/Ticket";
import { PwResetRequest } from "./pages/PwResetRequest";
import { PwReset } from "./pages/PwReset";
import { EmailConfirm } from "./pages/EmailConfirm";
import { Chapter3 } from "./pages/chapter3";

export const App: React.FC = () => (
@@ -27,6 +35,15 @@ export const App: React.FC = () => (

<Route path="/blog/:slug" component={Post} />
<Route path="/blog" component={Blog} />
<Route path="/signup" component={SignUp} />
<Route path="/signin" component={SignIn} />
<Route path="/dashboard" component={Dashboard} />
<Route path="/ticket/:id" render={(props) => <Ticket {...props} />} />
<Route path="/edit" component={Edit} />
<Route path="/request_reset" component={PwResetRequest} />
<Route path="/reset_password" component={PwReset} />
<Route path="/email_confirm" component={EmailConfirm} />

<Route exact path="/" component={Stories} />
<Route component={NotFound} />
</Switch>
62 changes: 62 additions & 0 deletions src/components/blog/feed/filter-control/categories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import cn from "classnames";
import React, { useEffect, useState } from "react";
import { getTopics } from "../../../../users_api/UserAPI";
import TopicInterface from "../../../user-dashboard";
import { useCategories } from "../../wp/categories";
import { useFilteredCategories } from "../fitering";

@@ -23,7 +26,53 @@ const Button: React.FC<{ onClick(): void; active: boolean }> = ({
};

export function CategoriesFilterControl() {
const [user, setUser] = useState(undefined);
const [userTopicsSelected, setUserTopicsSelected] = useState<boolean>(false);
const [topics, setTopics] = useState<TopicInterface[]>([]);


useEffect(() => {
const loggedInUser = localStorage.getItem('user');
if (loggedInUser) {
const foundUser = JSON.parse(loggedInUser);
setUser(foundUser);
fetchTopics();
} else {
setUser(undefined);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [localStorage.getItem('user')]);

const { loading, error, data } = useCategories();

const selectUserAll = () => {
if (data) {
topics.forEach((topic) => {
const foundCategory = data.categories.nodes.find((category) => category.name === topic.name);
if (foundCategory) {
select(foundCategory);
}
})
}
}

const fetchTopics = async () => {
let response;
try {
response = await getTopics()
} catch(error) {
setTopics([])
}
let responseTopics: Array<TopicInterface> = []
if (response) {
// @ts-ignore
response.forEach(element => {
responseTopics.push(element)
});
setTopics(responseTopics)
}
}

const {
items: selectedCategories,
add: select,
@@ -38,6 +87,19 @@ export function CategoriesFilterControl() {
<Button active={!selectedCategories.length} onClick={unselectAll}>
Alle Kategorien
</Button>
{ user && (
<Button active={userTopicsSelected} onClick={() => {
if (!userTopicsSelected) {
selectUserAll();
setUserTopicsSelected(true);
} else {
unselectAll();
setUserTopicsSelected(false);
}
}}>
Meine Kategorien
</Button>
)}
{data.categories.nodes.map((category, i) => {
const isSelected = selectedCategories.indexOf(category) >= 0;

51 changes: 39 additions & 12 deletions src/components/blog/feed/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,39 @@
import React from "react";
import React, { useEffect, useState } from "react";

import { useFeed, FeedItemData } from "../wp";
import { Post } from "./post";
import { FilterControl } from "./filter-control";
import { Spinner } from "../../spinner";
import { SortMethodSelection, defaultSortMethod, SortMethod } from "./sorting";
import { favoritPosts } from "../../../users_api/UserAPI";

function PostList({ sortMethod }: { sortMethod: SortMethod<FeedItemData> }) {
const [favoritposts, setFavoritPosts] = useState([]);
const [loaded, setLoaded] = useState<boolean>(false);
const { loading, error, data } = useFeed();
const [firstRenderDone, setFirstRenderDone] = React.useState(false);
const [firstRenderDone, setFirstRenderDone] = useState(false);

React.useEffect(() => {
const fetchFavorits = async () => {
let response;
try {
response = await favoritPosts();
} catch (error) {
setFavoritPosts([]);
}
// @ts-ignore
let slugs = response.slugs;
setFavoritPosts(JSON.parse(JSON.stringify(slugs)));
setLoaded(true);
};

useEffect(() => {
const loggedInUser = localStorage.getItem("user");

if (loggedInUser) {
fetchFavorits();
} else {
setLoaded(true);
}
if (data) setFirstRenderDone(true);
}, [data]);

@@ -26,15 +49,19 @@ function PostList({ sortMethod }: { sortMethod: SortMethod<FeedItemData> }) {

if (loading && firstRenderDone) return <Spinner />;

return (
<div className="grid grid-cols-1 gap-x-8 gap-y-12 sm:grid-cols-2 md:gap-x-10 xl:gap-16 2xl:grid-cols-3">
{(items[0] ? (items as FeedItemData[]).sort(sortMethod.fn) : items).map(
(data, i) => (
<Post data={data} key={i} />
)
)}
</div>
);
if (loaded) {
return (
<div className="grid grid-cols-1 gap-x-8 gap-y-12 sm:grid-cols-2 md:gap-x-10 xl:gap-16 2xl:grid-cols-3">
{(items[0] ? (items as FeedItemData[]).sort(sortMethod.fn) : items).map(
(data, i) => (
<Post data={data} key={i} favorits={favoritposts} />
)
)}
</div>
);
}

return <Spinner />
}

export const Feed: React.FC = () => {
7 changes: 5 additions & 2 deletions src/components/blog/feed/post/index.tsx
Original file line number Diff line number Diff line change
@@ -9,8 +9,10 @@ import { BookmarkButton } from "../../single/bookmark-button";
import { DetailBar } from "./detail-bar";

import styles from "./post.module.css";
import { LatestPostData } from "../../wp/latest_post";

export type ParsedPostData = {
slug: string;
path: string;
title: string;
excerpt: string;
@@ -22,10 +24,11 @@ export type ParsedPostData = {
tags?: TagData[];
};

export const Post: React.FC<{ data?: FeedItemData }> = ({ data }) => {
export const Post: React.FC<{ data?: FeedItemData | LatestPostData, favorits: string[]}> = ({ data, favorits }) => {
const parsedData = React.useMemo<ParsedPostData | undefined>(
() =>
data && {
slug: data.slug,
path: `/blog/${data.slug}`,
title: data.title,
excerpt: data.excerpt,
@@ -84,7 +87,7 @@ export const Post: React.FC<{ data?: FeedItemData }> = ({ data }) => {
</div>
)}
<div className="absolute right-1 -top-5">
<BookmarkButton disabled={!parsedData} />
<BookmarkButton slug={parsedData ? parsedData.slug : ""} disabled={!parsedData} posts={favorits} />
</div>
</div>
);
40 changes: 36 additions & 4 deletions src/components/blog/single/bookmark-button/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,52 @@
import React from "react";
import React, { useEffect, useState } from "react";
import cn from "classnames";

import { BookmarkIcon } from "./icon";

import styles from "./bookmark.module.css";
import { deFavorisePost, favorisePost } from "../../../../users_api/UserAPI";

export const BookmarkButton: React.FC<{
disabled?: boolean;
onSolidBackground?: boolean;
}> = ({ disabled, onSolidBackground }) => {
const [active, setActive] = React.useState(false);
slug: string;
posts: string[];
}> = ({ disabled, onSolidBackground, slug, posts }) => {
const [active, setActive] = useState(false);

const updateFavorise = async (slug: string) => {
let favorite_blogpost = {
slug,
};
if (active) {
try {
await deFavorisePost(favorite_blogpost);
} catch (error) {
}
} else {
try {
await favorisePost(favorite_blogpost);
} catch (error) {
}
}
};


useEffect(() => {
if (posts.includes(slug)) {
setActive(true);
}
}, [posts, slug]);



return (
<button
className={cn(styles.button, "group", active && styles.active)}
onClick={() => setActive(!active)}
onClick={() => {
setActive(!active);
updateFavorise(slug);
}}
disabled={disabled}
>
<div className="pointer-events-none">
8 changes: 4 additions & 4 deletions src/components/blog/single/heading.tsx
Original file line number Diff line number Diff line change
@@ -4,19 +4,19 @@ import type { PostData } from "../wp";
import { PostDate } from "./date";
import { BookmarkButton } from "./bookmark-button";

export const PostHeading: React.FC<PostData> = ({ post }) => (
export const PostHeading: React.FC<{ post: PostData, favorits: string[]}> = ({ post, favorits }) => (
<div className="flex max-w-full">
<div className="flex-1 mr-4">
<div className="mb-2">
<PostDate date={new Date(post.date)} />
<PostDate date={new Date(post.post.date)} />
</div>
<h1
className="text-xl font-bold sm:font-normal sm:text-3xl uppercase text-em3 leading-snug md:leading-snug mb-16"
dangerouslySetInnerHTML={{ __html: post.title }}
dangerouslySetInnerHTML={{ __html: post.post.title }}
/>
</div>
<div className="flex-shrink-0">
<BookmarkButton onSolidBackground={true} />
<BookmarkButton onSolidBackground={true} slug={post.post.slug} posts={favorits} />
</div>
</div>
);
24 changes: 22 additions & 2 deletions src/components/blog/single/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
import React from "react";
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";

import { SEO } from "../../seo";
import { usePost } from "../wp";
import { PostHeading } from "./heading";
import { PostContent } from "./content";
import { Spinner } from "../../spinner";
import { favoritPosts } from "../../../users_api/UserAPI";

export const SinglePost: React.FC = () => {
const [favoritposts, setFavoritPosts] = useState([]);
const { slug } = useParams<{ slug: string }>();
const { loading, error, data } = usePost(slug);

useEffect(() => {
const loggedInUser = localStorage.getItem('user');
if (loggedInUser) {
const fetchFavorits = async () => {
let response;
try {
response = await favoritPosts()
} catch(error) {
setFavoritPosts([])
}
// @ts-ignore
let slugs = response.slugs
setFavoritPosts(JSON.parse(JSON.stringify(slugs)))
}
fetchFavorits();
}
}, [])

return (
<div className="center-box max-w-2xl">
{loading ? (
@@ -20,7 +40,7 @@ export const SinglePost: React.FC = () => {
) : (
<>
<SEO title={data.post.title} />
<PostHeading post={data.post} />
<PostHeading post={data} favorits={favoritposts} />
<PostContent post={data.post} />
</>
)}
Loading