Skip to content
This repository has been archived by the owner on Oct 4, 2024. It is now read-only.

FE: API, Code Cleanup & Fixes #222

Merged
merged 18 commits into from
Jun 8, 2024
Merged
Show file tree
Hide file tree
Changes from 14 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
3 changes: 2 additions & 1 deletion frontend/.env.example
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
VITE_BACKEND_URL=http://localhost:3000/
VITE_BACKEND_URL=http://localhost:3000/
VITE_AUTHOR_NAME_MAX_LENGTH=12
8 changes: 8 additions & 0 deletions frontend/src/components/avatar/Avatar.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.avatar {
border-radius: 50%;
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5));
height: 40px !important;
object-fit: cover;
pointer-events: none;
width: 40px !important;
}
12 changes: 12 additions & 0 deletions frontend/src/components/avatar/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import "./Avatar.scss";
import React from "react";

export default function Avatar(props: { userId?: string }) {
return <img className={ "avatar" }
src={ "" }
alt={ "Avatar" }
onError={ ({ currentTarget }) => {
currentTarget.onerror = null; // prevents looping
currentTarget.src = "https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50";
} }/>
}
69 changes: 69 additions & 0 deletions frontend/src/components/questionpreview/QuestionAnswer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Answer } from "../../def/Question";
import React from "react";
import Avatar from "../avatar/Avatar";

/**
* Renders an answer to be displayed in QuestionView
* @param props holds the answer and an index
*/
export default function QuestionAnswer(props: { answer: Answer, index: number }) {
return <div key={ props.index } className={ "container transparent question-answer" }>
<div className={ "question-answer-author" }
style={ {
paddingTop: props.answer.author.type === "ai" ? "var(--spacing)" : 0,
paddingInline: props.answer.author.type === "ai" ? "var(--spacing)" : 0
} }>
{ props.answer.author.type === "ai" ? <>
<i className={ "fi fi-sr-brain" }
style={ {
fontSize: "2em",
height: "auto",
color: "var(--primary-color)",
filter: "drop-shadow(0 0 5px rgba(0, 0, 0, 0.2))"
} }/>

<p style={ { paddingTop: "calc(var(--spacing) * 0.5)" } }>
<span>Simp</span>
</p>
</> : <div className={ "question-answer-author-user" } tabIndex={ 0 }>
<Avatar userId={ props.answer.author.id }/>

<p>
<span>{ props.answer.author.name }</span>
<span className={ "badge" }>{ props.answer.author.type.toUpperCase() }</span>
</p>
</div> }

<span className={ "caption" }>replied { props.answer.created }</span>
</div>

<div className={ "question-answer-text" }>
<div className={ "glass" + (props.answer.author.type === "ai" ? " glass-simp" : "") }>
<p>{ props.answer.content }</p>
</div>

<div className={ "question-answer-actions" }>
<div
className={ "question-answer-actions-rate" + (props.answer.opinion === "like" ? " rating" : "") }>
<i className={ "fi fi-rr-social-network primary-icon" } tabIndex={ 0 }/>
<span className={ "question-figure" }>{ props.answer.likes }</span>
<span className={ "question-unit" }>likes</span>
</div>

<div
className={ "question-answer-actions-rate" + (props.answer.opinion === "dislike" ? " rating" : "") }>
<i className={ "fi fi-rr-social-network flipY primary-icon" } tabIndex={ 0 }/>
<span className={ "question-figure" }>{ props.answer.dislikes }</span>
<span className={ "question-unit" }>dislikes</span>
</div>

<div style={ { flex: 1 } }/>

<button className={ "question-report" }>
<i className={ "fi fi-rr-flag" }/>
Report answer
</button>
</div>
</div>
</div>
}
9 changes: 5 additions & 4 deletions frontend/src/components/questionpreview/QuestionPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import "./QuestionPreview.scss";
import React from "react";
import { useNavigate } from "react-router-dom";
import { Question } from "../../def/Question";
import Avatar from "../avatar/Avatar";

interface Props {
question: Question;
Expand Down Expand Up @@ -63,15 +64,15 @@ export default function QuestionPreview(props: Props) {
<div className={ "question-details-wrapper" }>
<div className={ "question-stats" }>
<div
className={ "question-stat" + (props.question.rating === "like" ? " rating" : "") }>
className={ "question-stat" + (props.question.opinion === "like" ? " rating" : "") }>
<i className={ "fi fi-rr-social-network primary-icon" }/>
<span
className={ "question-figure" }>{ props.question.likes.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") }</span>
<span className={ "question-unit" }>likes</span>
</div>

<div
className={ "question-stat" + (props.question.rating === "dislike" ? " rating" : "") }>
className={ "question-stat" + (props.question.opinion === "dislike" ? " rating" : "") }>
<i className={ "fi fi-rr-social-network primary-icon flipY" }/>
<span
className={ "question-figure" }>{ props.question.dislikes.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") }</span>
Expand All @@ -96,10 +97,10 @@ export default function QuestionPreview(props: Props) {
</div>

<div className={ "author" }>
<img className={ "avatar" } src={ "https://www.w3schools.com/w3images/avatar2.png" } alt={ "Avatar" }/>
<Avatar userId={ props.question.author.id }/>
<p style={ { margin: 0, display: "flex", flexDirection: "column" } }>
<span className={ "caption" }>Asked by</span>
<span>{ props.question.author.name }</span>
<span>{ props.question.author.name.substring(0, import.meta.env.VITE_AUTHOR_NAME_MAX_LENGTH) }</span>
</p>
</div>
</div>
Expand Down
73 changes: 39 additions & 34 deletions frontend/src/components/questionpreview/QuestionPreviewSkeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,52 @@ import React from "react";
/**
* Renders a skeleton, based on QuestionPreview
*/
export default function QuestionPreviewSkeleton() {
return <div className={ "container questions-question" } style={ { cursor: "auto" } }>
<div className={ "question" }>
<Skeleton containerClassName={ "tags" }/>

<h2>
<Skeleton width={ 400 }/>
</h2>

<Skeleton containerClassName={ "caption" } width={ 300 }/>
</div>

<div className={ "question-details-wrapper" }>
<div className={ "question-stats" }>
<div className={ "question-stat" }>
<Skeleton width={ 80 } height={ 24 }/>
</div>
export default function QuestionPreviewSkeleton(props: { count?: number }) {
const { count = 1 } = props;
const skeletonArray = Array.from({ length: count });

return skeletonArray.map((_, i) => {
return <div className={ "container questions-question" } style={ { cursor: "auto" } } key={ i }>
<div className={ "question" }>
<Skeleton containerClassName={ "tags" }/>

<div className={ "question-stat" }>
<Skeleton width={ 80 } height={ 24 }/>
</div>
<h2>
<Skeleton width={ 400 }/>
</h2>

<Skeleton containerClassName={ "caption" } width={ 300 }/>
</div>

<div className={ "question-stats" }>
<div className={ "question-stat" }>
<Skeleton width={ 80 } height={ 24 }/>
<div className={ "question-details-wrapper" }>
<div className={ "question-stats" }>
<div className={ "question-stat" }>
<Skeleton width={ 80 } height={ 24 }/>
</div>

<div className={ "question-stat" }>
<Skeleton width={ 80 } height={ 24 }/>
</div>
</div>

<div className={ "question-stat" }>
<Skeleton width={ 80 } height={ 24 }/>
<div className={ "question-stats" }>
<div className={ "question-stat" }>
<Skeleton width={ 80 } height={ 24 }/>
</div>

<div className={ "question-stat" }>
<Skeleton width={ 80 } height={ 24 }/>
</div>
</div>
</div>

<div className={ "author" }>
<Skeleton circle width={ 40 } height={ 40 }/>

<p style={ { margin: 0, display: "flex", flexDirection: "column" } }>
<Skeleton width={ 80 } height={ 12 }/>
<Skeleton width={ 80 } height={ 20 }/>
</p>
<div className={ "author" }>
<Skeleton circle width={ 40 } height={ 40 }/>

<p style={ { margin: 0, display: "flex", flexDirection: "column" } }>
<Skeleton width={ 80 } height={ 12 }/>
<Skeleton width={ 80 } height={ 20 }/>
</p>
</div>
</div>
</div>
</div>
})
}
6 changes: 3 additions & 3 deletions frontend/src/def/Question.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export interface Question {
tags: string[];
likes: number;
dislikes: number;
rating: "like" | "dislike" | "none";
opinion: "like" | "dislike" | "none";
answers: number;
created: string;
updated: string;
Expand All @@ -19,12 +19,12 @@ export interface Answer {
created: string;
likes: number;
dislikes: number;
rating: "like" | "dislike" | "none";
opinion: "like" | "dislike" | "none";
author: Author;
}

interface Author {
id: string;
name: string;
type: "user" | "pro" | "ai";
type: "guest" | "user" | "pro" | "admin" | "ai";
}
9 changes: 0 additions & 9 deletions frontend/src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,6 @@ code {
}
}

.avatar {
border-radius: 50%;
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5));
height: 40px !important;
object-fit: cover;
pointer-events: none;
width: 40px !important;
}

::selection {
background-color: var(--primary-color);
color: var(--primary-color-contrast);
Expand Down
11 changes: 3 additions & 8 deletions frontend/src/pages/dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import Search from "../../components/search/Search";
import { axiosError } from "../../def/axios-error";
import { useAlert } from "react-alert";
import Avatar from "../../components/avatar/Avatar";

// ory setup
const basePath = "http://localhost:4000"
Expand All @@ -39,9 +40,9 @@
const { t } = useTranslation();
const navigate = useNavigate();
const [session, setSession] = useState<Session | undefined>();
const [logoutUrl, setLogoutUrl] = useState<string | undefined>();

Check warning on line 43 in frontend/src/pages/dashboard/Dashboard.tsx

View workflow job for this annotation

GitHub Actions / verify

'setStats' is assigned a value but never used
const [stats, setStats] = useState<{ streak: number, views: number, likes: number } | undefined>();

Check warning on line 44 in frontend/src/pages/dashboard/Dashboard.tsx

View workflow job for this annotation

GitHub Actions / verify

'setStats' is assigned a value but never used

Check warning on line 44 in frontend/src/pages/dashboard/Dashboard.tsx

View workflow job for this annotation

GitHub Actions / verify

'setHistory' is assigned a value but never used
const [history, setHistory] = useState<number[] | undefined>();

Check warning on line 45 in frontend/src/pages/dashboard/Dashboard.tsx

View workflow job for this annotation

GitHub Actions / verify

'setHistory' is assigned a value but never used

Check warning on line 45 in frontend/src/pages/dashboard/Dashboard.tsx

View workflow job for this annotation

GitHub Actions / verify

'setActiveQuestionName' is assigned a value but never used
const [activeQuestionName, setActiveQuestionName] = useState<string | undefined>();

const getUserName = (identity?: Identity) =>
Expand All @@ -56,7 +57,7 @@
if (window.location.pathname.includes("/question")) {
let questionId = window.location.pathname.split("/question/")[1].substring(0, 36);
global.axios.get("question/" + questionId + "/title")
.then(res => console.log(res))
.then(res => setActiveQuestionName(res.data.title))
.catch(err => axiosError(err, alert));
}
}
Expand Down Expand Up @@ -168,13 +169,7 @@
boxShadow: "var(--box-shadow)"
} }
tabIndex={ 0 }>
<img className={ "avatar" }
src={ session !== undefined ? "https://benniloidl.de/static/media/me.6c5597f7d72f68a1e83c.jpeg" : "" }
alt={ "Avatar" }
onError={ ({ currentTarget }) => {
currentTarget.onerror = null; // prevents looping
currentTarget.src = "https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50";
} }/>
<Avatar userId={ session?.identity?.id }/>
</div> }
items={ [
{
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/pages/dashboard/editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ export default function Editor(props: {}) {
tags: tags,
content: description,
useAI: questionType === "simp"
}, {
withCredentials: true
})
.then(res => {
alert.info(res.status);
Expand Down
42 changes: 42 additions & 0 deletions frontend/src/pages/dashboard/question/QuestionAnswerSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Skeleton from "react-loading-skeleton";
import React from "react";

/**
* Displays the skeleton for QuestionAnswer while the data is being fetched
* @param props optional count of how many skeletons should be displayed
*/
export default function QuestionAnswerSkeleton(props: { count?: number }) {
const { count = 1 } = props;
const skeletonArray = Array.from({ length: count });

return skeletonArray.map((_, i) => {
return <div className={ "container transparent question-answer" } key={ i }>
<div className={ "question-answer-author" }>
<Skeleton height={ 40 } width={ 40 }/>

<p>
<Skeleton height={ 20 } width={ 100 }/>
</p>

<span className={ "caption" }><Skeleton width={ 60 }/></span>
</div>

<div className={ "question-answer-text" }>
<div className={ "glass" }>
<p>
<Skeleton count={ 3 }/>
</p>
</div>

<div className={ "question-answer-actions" }>
<Skeleton height={ 20 } width={ 100 }/>
<Skeleton height={ 20 } width={ 100 }/>

<div style={ { flex: 1 } }/>

<Skeleton height={ 20 } width={ 200 }/>
</div>
</div>
</div>
})
}
47 changes: 47 additions & 0 deletions frontend/src/pages/dashboard/question/QuestionStats.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import Skeleton from "react-loading-skeleton";
import React from "react";
import { Question } from "../../../def/Question";

export default function QuestionStats(props: {question?: Question}) {
return <>
<span className={ "caption" }>Question Stats</span>
<div className={ "question-stats" }>
{ props.question ? <>
<div className={ "question-stat" }>
<i className={ "fi fi-rr-eye primary-icon" }/>
<span
className={ "question-figure" }>{ "0".replace(/\B(?=(\d{3})+(?!\d))/g, ",") }</span>
<span className={ "question-unit" }>views</span>
</div>

<div
className={ "question-stat" + (props.question.opinion === "like" ? " rating" : "") }>
<i className={ "fi fi-rr-social-network primary-icon" } tabIndex={ 0 }/>
<span
className={ "question-figure" }>{ props.question.likes.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") }</span>
<span className={ "question-unit" }>likes</span>
</div>

<div
className={ "question-stat" + (props.question.opinion === "dislike" ? " rating" : "") }>
<i className={ "fi fi-rr-social-network flipY primary-icon" } tabIndex={ 0 }/>
<span
className={ "question-figure" }>{ props.question.dislikes.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") }</span>
<span className={ "question-unit" }>dislikes</span>
</div>

<div className={ "question-stat" }>
<i className={ "fi fi-rr-comment-dots primary-icon" }/>
<span
className={ "question-figure" }>{ props.question.answers.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") }</span>
<span className={ "question-unit" }>answers</span>
</div>
</> : <>
<Skeleton containerClassName={ "question-stat" } width={ 80 }/>
<Skeleton containerClassName={ "question-stat" } width={ 80 }/>
<Skeleton containerClassName={ "question-stat" } width={ 80 }/>
<Skeleton containerClassName={ "question-stat" } width={ 80 }/>
</> }
</div>
</>
}
Loading
Loading