profile
viewpoint
Peter Manser petermanser smartive Zürich, Switzerland https://petermanser.ch

dbrgn/pyxtra 11

A small commandline utility written in Python to access the (now dead) Swisscom Xtrazone SMS service

petermanser/Jira-cards-printing 2

Print Jira stories in your own formatted way

petermanser/Analysis-Formelsammlung 1

Analysis Formelsammlung basierend auf dem Schulstoff der Hochschule für Technik Rapperswil (HSR)

petermanser/emoji-cheat-sheet.com 1

A one pager for emojis on Campfire and GitHub

petermanser/flask 1

A microframework based on Werkzeug, Jinja2 and good intentions

petermanser/Flask-FlatPages 1

Provides flat static pages to a Flask application

petermanser/flask-login 1

A GitHub mirror of flask-login

petermanser/Forge-Bootstrap 1

An example template app, showing the use of Backbone.js and Zepto.js in a Forge app.

petermanser/gesagt-im-parlament.ch 1

A swiss parliament related project built during #MakeOpenData

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

 {-  "name": "cas-fee-adv-qwacker-tpl",-  "version": "0.0.0-development",+  "name": "app-yeahyeahyeah",+  "version": "0.1.0",   "private": true,   "scripts": {     "dev": "next dev",     "build": "next build",     "start": "next start",-    "lint": "next lint"+    "lint": "next lint",+    "lint:fix": "next lint --fix",+    "format:check": "prettier --check {data,hooks,services,src}/**/*.{js,jsx,ts,tsx,css,md,json} --config ./.prettierrc",

prettier --check 'src/**/*.{ts,tsx}' 'data/*.json' --config ./.prettierrc

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+FROM node:16-alpine+ARG NPM_TOKEN+WORKDIR /usr/src/app+RUN npm install --global pm2+COPY .npmrc ./+RUN echo "//npm.pkg.github.com/:_authToken=${NPM_TOKEN}" >> .npmrc+COPY package*.json ./+RUN npm install

npm ci

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

 /** @type {import('next').NextConfig} */ const nextConfig = {   reactStrictMode: true,+  transpilePackages: ['@acme/ui', 'lodash-es'],   swcMinify: true,-}+  images: {+    remotePatterns: [+      {+        protocol: 'https',+        hostname: 'storage.googleapis.com',+        port: '',+        pathname: '/qwacker-api-prod-data/**',+      },+    ],+    minimumCacheTTL: 1500000,+  },+  env: {+    NEXT_PUBLIC_URL: process.env.NEXT_PUBLIC_URL,+  },+}; -module.exports = nextConfig+module.exports = nextConfig;

<CHECK>

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import type { NextApiRequest, NextApiResponse } from 'next';++type Data = {+  name: string;+};++export default function handler(req: NextApiRequest, res: NextApiResponse<Data>) {+  const { method, query } = req;+  console.info({ method, query });

console.log wurde wohl zu Debug zwecken eingeführt. Könnte wohl entfernt werden.

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import axios from 'axios';+import { transformMumble, QwackerMumbleResponse } from '../types/qwacker';++export const fetchMumbles = async (params: {+  limit?: number;+  offset?: number;+  newerThanMumbleId?: string;+  token?: string;+}) => {+  const { limit, offset, newerThanMumbleId, token } = params || {};++  let searchParams = new URLSearchParams({+    limit: limit?.toString() || '10',+    offset: offset?.toString() || '0',+  });++  if (newerThanMumbleId)+    searchParams = new URLSearchParams({+      newerThan: newerThanMumbleId,+    });++  const url = `${process.env.NEXT_PUBLIC_QWACKER_API_URL}/posts?${searchParams}`;++  try {+    const { data, count } = (+      await axios.get(url, {+        headers: {

Ein Helper für die Headers und weiteren Elementen wäre sinnvoll

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import React from 'react';+import Link from 'next/link';+import tw, { styled } from 'twin.macro';+import Message from '../../../data/content.json';+import useSWR from 'swr';+import { useSession } from 'next-auth/react';+import { elapsedTime } from '@/utils/timeConverter';+import { fetchUser, Mumble, QwackerUserResponse } from '@/services';+import {+  Avatar,+  CommentButton,+  Cancel,+  IconLink,+  User,+  Paragraph,+} from '@smartive-education/design-system-component-library-yeahyeahyeah';+import { Like } from './Like';+import { Share } from './Share';+import { Picture } from './Picture';+import { renderHashtags } from './Hashtag';++type MumbleProps = {+  type: string;+  $isReply?: boolean;+  handleDeleteCallback?: (id: string) => void;+  fallbackUsers?: QwackerUserResponse;+} & Mumble;++export const Post: React.FC<MumbleProps> = ({+  id,+  creator,+  text,+  mediaUrl,+  createdTimestamp,+  likeCount,+  likedByUser,+  replyCount,+  type,+  $isReply,+  handleDeleteCallback,+  fallbackUsers,+}) => {+  const { data: session }: any = useSession();+  const { data } = useSWR(

Wieso ladet ihr hier den User nochmals? Das bringt unnötige Komplexität und Ladezyklen.

Wenn ich demo-mässig diese Zeile ersetze durch:

const data = fallbackUsers?.data?.find((x) => x.id === creator);

... sehe ich keine Änderung.

Diese Komponente sollte keine Datenlogik mehr haben, sondern nur die Daten rendern.

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import React from 'react';+import { FetchMumbles } from '@/types/fallback';+import { Button, Container, Heading } from '@smartive-education/design-system-component-library-yeahyeahyeah';+import { KeyedMutator } from 'swr';+import tw from 'twin.macro';+import { WelcomeText } from '../content/WelcomeText';+import { Alert } from '../alert/Alert';+import { TextBoxComponent } from '../form/TextBoxComponent';+import { Hashtag } from './Hashtag';+import { User } from '@/services';++type TimelineProps = {+  mutate: KeyedMutator<FetchMumbles[]>;+  checkForNewMumbles: boolean;+  quantityNewMumbles: string;+  handleRefreshPage: () => void;+  renderMumbles: () => JSX.Element;+  hashtag?: string;+  creator?: { id: string };+  fallbackUserLoggedIn?: User;+};++export const Timeline: React.FC<TimelineProps> = ({+  mutate,+  checkForNewMumbles,+  quantityNewMumbles,+  handleRefreshPage,+  renderMumbles,+  hashtag,+  creator,+  fallbackUserLoggedIn,+}) => {+  return (+    <>+      {!hashtag && checkForNewMumbles && (+        <MumbleMessageBox>+          <Button label={quantityNewMumbles} color="gradient" onClick={handleRefreshPage} size="small" width="full" />+        </MumbleMessageBox>+      )}+      <Container layout="plain">+        {!hashtag && !creator && (+          <>+            <WelcomeText />+            <Alert />+            <TextBoxComponent variant="write" mutate={mutate} fallbackUserLoggedIn={fallbackUserLoggedIn} />+          </>+        )}+        {hashtag && (

Gehört wohl auch auf die hashtag Page, da es ja nur da genutzt wird.

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import React, { useState } from 'react';+import { GetServerSideProps, GetServerSidePropsContext } from 'next';+import { NextSeo } from 'next-seo';+import tw from 'twin.macro';+import Message from '../../../data/content.json';+import { getToken } from 'next-auth/jwt';+import { useSession } from 'next-auth/react';+import { fetchMyLikes, fetchMyMumbles, fetchUser, fetchUsers, QwackerUserResponse, User } from '@/services';+import { FetchMumbles } from '@/types/fallback';+import { Header, Stream } from '@/components';+import { Container, Switch } from '@smartive-education/design-system-component-library-yeahyeahyeah';++type HeaderProps = {+  creator: any;

wieso nicht getyped? (any)

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import React from 'react';+import { FetchMumbles } from '@/types/fallback';+import { useStream } from '@/hooks';+import { MumbleFetcher, SearchMumblesFetcher } from '@/types/swr';+import { Listing } from '../mumble/Listing';+import { LoadingSpinner } from '../loading/LoadingSpinner';+import { Timeline } from '../mumble/Timeline';+import { TextBoxComponent } from '../form/TextBoxComponent';+import { Alert } from '../alert/Alert';+import { QwackerUserResponse, User, alertService } from '@/services';++type StreamProps = {+  url: string;+  limit: number;+  fallback: FetchMumbles;+  fallbackUsers?: QwackerUserResponse;+  fallbackUserLoggedIn?: User;+  fetcher: MumbleFetcher | SearchMumblesFetcher;+  id?: string;+  hashtag?: string;+  creator?: { id: string };+};++export const Stream: React.FC<StreamProps> = ({+  limit,+  fallback,+  hashtag,+  fetcher,+  creator,+  url,+  id,+  fallbackUsers,+  fallbackUserLoggedIn,+}) => {+  const [+    data,+    mutate,+    error,+    isValidating,+    isLoading,+    checkForNewMumbles,+    quantityNewMumbles,+    renderTimeline,+    handleDelete,+    handleRefreshPage,+    ref,+  ] = useStream(url, limit, fallback, fetcher, id, hashtag, creator);++  const renderMumbles = (isReply?: boolean) => {+    return (+      <>+        {data && <Listing data={data} handleDelete={handleDelete} isReply={isReply} fallbackUsers={fallbackUsers} />}+        <div key="last" tw="invisible" ref={ref} />+        <div tw="h-16 mb-32">{(isLoading || isValidating) && <LoadingSpinner />}</div>+      </>+    );+  };++  if (error) {+    alertService.error(`${error}`, {+      autoClose: false,+      keepAfterRouteChange: false,+    });+    return <Alert />;+  }++  return (+    <>+      {renderTimeline ? (

Verstehe hier die Logik nicht ganz: Was ist der Unterschied zwischen Timeline, renderMumbles(true) und renderMumbles() (Entscheidung basierend auf id).

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import React from 'react';+import { FetchMumbles } from '@/types/fallback';+import { useStream } from '@/hooks';+import { MumbleFetcher, SearchMumblesFetcher } from '@/types/swr';+import { Listing } from '../mumble/Listing';+import { LoadingSpinner } from '../loading/LoadingSpinner';+import { Timeline } from '../mumble/Timeline';+import { TextBoxComponent } from '../form/TextBoxComponent';+import { Alert } from '../alert/Alert';+import { QwackerUserResponse, User, alertService } from '@/services';++type StreamProps = {+  url: string;+  limit: number;+  fallback: FetchMumbles;+  fallbackUsers?: QwackerUserResponse;+  fallbackUserLoggedIn?: User;+  fetcher: MumbleFetcher | SearchMumblesFetcher;+  id?: string;+  hashtag?: string;+  creator?: { id: string };+};++export const Stream: React.FC<StreamProps> = ({+  limit,+  fallback,+  hashtag,+  fetcher,+  creator,+  url,+  id,+  fallbackUsers,+  fallbackUserLoggedIn,+}) => {+  const [+    data,+    mutate,+    error,+    isValidating,+    isLoading,+    checkForNewMumbles,+    quantityNewMumbles,+    renderTimeline,+    handleDelete,+    handleRefreshPage,+    ref,+  ] = useStream(url, limit, fallback, fetcher, id, hashtag, creator);++  const renderMumbles = (isReply?: boolean) => {

wieso nutzt ihr nicht den type auf dem Mumble? Dieses Property braucht es nicht.

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import axios from 'axios';+import { transformMumble, QwackerMumbleResponse } from '../types/qwacker';++export const fetchMumbles = async (params: {+  limit?: number;+  offset?: number;+  newerThanMumbleId?: string;+  token?: string;+}) => {+  const { limit, offset, newerThanMumbleId, token } = params || {};++  let searchParams = new URLSearchParams({+    limit: limit?.toString() || '10',+    offset: offset?.toString() || '0',+  });++  if (newerThanMumbleId)+    searchParams = new URLSearchParams({

searchParams.append('newerThan', newerThanMumbleId);

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import { UploadImage } from '@/services';++type TextBoxState = {+  errorMessage: string;+  fileUploadError: string;+  file: UploadImage | null;+  showModal: boolean;+  inputValue: string;+};++type TextBoxAction = {

Hier würden sich Union Types anbieten: So können keine falschen Types/Kombis übergeben werden.

type TextBoxAction =
  | { type: 'SET_ERROR_MESSAGE'; payload: string }
  | { type: 'CLEAR_ERROR_MESSAGE' }
petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import type { NextApiRequest, NextApiResponse } from 'next';++type Data = {+  name: string;+};++export default function handler(req: NextApiRequest, res: NextApiResponse<Data>) {

braucht ihr dieses File?

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

-This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).+![Mumble Theme](https://raw.githubusercontent.com/smartive-education/design-system-component-library-yeahyeahyeah/master/packages/design-system-component-library-yeahyeahyeah/stories/assets/mumble-logo.svg?style=for-the-badge) -## Getting Started+# Mumble Chat App -First, run the development server:+## Introduction++This is the final project for the 2nd part of the **Frontend Engineering Advanced** course, offered by the **University Ost** in Rapperswil.++We had to develop a **Chat App** based on [NextJS](https://nextjs.org) in a given **Mumble Design** according to the specification of [Mumble-Figma-Design](https://www.figma.com/file/nsXR2h0KwciWpuwKRD58FX/Mumble?node-id=437-1018). We had the requirement to use our previously developed [Component Library](https://github.com/smartive-education/design-system-component-library-yeahyeahyeah) for our **Chat App**.++## Table of contents++- [Getting started](#getting-started)+- [Add Credentials](#add-credentials)+- [Add .env vars](#add-env-vars)+- [Install dependencies](#install-dependencies)+- [Resources](#resources)++## Getting started++In the next steps you will setup the chat app.++### Prerequisites++#### NODE++Please use node version **16.19.1**. If you use _nvm_ you can use the following command.++```shell+nvm use 16.19.1+```++## Add credentials++We need a github token and a .npmrc to get access to the [mumble npm package](https://github.com/smartive-education/design-system-component-library-yeahyeahyeah/pkgs/npm/design-system-component-library-yeahyeahyeah) at [smartive education](https://github.com/smartive-education) on github.++Create github token and add to .npmrc++[Create a classic github token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token#creating-a-personal-access-token-classic)++To authenticate by adding your personal access token (classic) to your ~/.npmrc file, edit the ~/.npmrc file for your project to include the following line, replacing TOKEN with your personal access token. Create a new ~/.npmrc file if one doesn’t exist.++```bash+//npm.pkg.github.com/:_authToken=TOKEN+```++Create .npmrc in the project folder, where you wanna add your npm package and add following line++```bash+@smartive-education:registry=https://npm.pkg.github.com+```++## Add .env vars++Create a new _.env_ file in the root directory. I have sent you an email with the _.env_ vars. Please copy them into the newly created _.env_ file.++Now, you should be able to start the application.++## Installation++```bash+git clone https://github.com/smartive-education/app-yeahyeahyeah.git++cd app-yeahyeahyeah++npm install

npm ci

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import React from 'react';+import { GetServerSideProps } from 'next';+import Message from '../../../data/content.json';+import { getToken } from 'next-auth/jwt';+import { FetchMumbles } from '@/types/fallback';+import { QwackerUserResponse, fetchUsers, searchMumbles } from '@/services';+import { Stream } from '@/components/stream/Stream';+import { NextSeo } from 'next-seo';++const HashtagPage = ({+  limit,+  fallback,+  fallbackUsers,+  hashtag,+}: {+  limit: number;+  fallback: FetchMumbles;+  fallbackUsers: QwackerUserResponse;+  hashtag: string;+}) => {+  return (+    <>+      <NextSeo title={`${Message.seo.search.title}`} description={`${Message.seo.search.description}`} />+      <Stream+        url="/api/mumbles"+        limit={limit}+        fallback={fallback}+        fallbackUsers={fallbackUsers}+        hashtag={hashtag}+        fetcher={searchMumbles}+      />+    </>+  );+};+export const getServerSideProps: GetServerSideProps<any> = async ({ req, query: { hashtag } }) => {+  const limit = 2;+  const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET });+  const mumbles: FetchMumbles = await searchMumbles({

Hier würde sich eine Auslagerung anbieten in einen Custom Hook useSearchMumbles. Oder sogar den Stream verwenden, welche ja auch weitere Logik wie new mumbles/load more hat.

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import React, { useState } from 'react';+import { GetServerSideProps, GetServerSidePropsContext } from 'next';+import { NextSeo } from 'next-seo';+import tw from 'twin.macro';+import Message from '../../../data/content.json';+import { getToken } from 'next-auth/jwt';+import { useSession } from 'next-auth/react';+import { fetchMyLikes, fetchMyMumbles, fetchUser, fetchUsers, QwackerUserResponse, User } from '@/services';+import { FetchMumbles } from '@/types/fallback';+import { Header, Stream } from '@/components';+import { Container, Switch } from '@smartive-education/design-system-component-library-yeahyeahyeah';++type HeaderProps = {+  creator: any;+  limit: number;+  fallbackUser: User;+  fallbackUsers: QwackerUserResponse;+  fallBackMyMumbles: FetchMumbles;+  fallBackMyLikes: FetchMumbles;+};++const ProfilePage = ({ creator, limit, fallbackUser, fallBackMyMumbles, fallBackMyLikes, fallbackUsers }: HeaderProps) => {+  const { data: session }: any = useSession();+  const [selection, setSelection] = useState('mumbles');++  const handleSelection = (value: string) => {+    setSelection(value);+  };++  return (+    <>+      <NextSeo+        title={`${session && session.user.firstname} ${session && session.user.lastname}'s mumble profile`}+        description={`Das Mumble-Profile von ${+          session && session.user.username+        }. Mumble, die Chat-App des CAS Frontend Engineer Advanced 2023.`}+        canonical={process.env.NEXT_PUBLIC_URL}+      />++      <Container layout="plain">+        <Header creator={creator} fallbackUser={fallbackUser} />++        {session?.user.id === creator.id ? (

hier würde es sich anbieten, das etwas auszulagern. ihr habt hier sehr verschachtelte Komponenten, was die Lesbarkeit schlechter macht.

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import React from 'react';+import { GetServerSideProps } from 'next';+import Message from '../../../data/content.json';+import { getToken } from 'next-auth/jwt';+import { FetchMumbles } from '@/types/fallback';+import { QwackerUserResponse, fetchUsers, searchMumbles } from '@/services';+import { Stream } from '@/components/stream/Stream';+import { NextSeo } from 'next-seo';++const HashtagPage = ({+  limit,+  fallback,+  fallbackUsers,+  hashtag,+}: {+  limit: number;+  fallback: FetchMumbles;+  fallbackUsers: QwackerUserResponse;+  hashtag: string;+}) => {+  return (+    <>+      <NextSeo title={`${Message.seo.search.title}`} description={`${Message.seo.search.description}`} />+      <Stream+        url="/api/mumbles"+        limit={limit}+        fallback={fallback}+        fallbackUsers={fallbackUsers}+        hashtag={hashtag}+        fetcher={searchMumbles}+      />+    </>+  );+};+export const getServerSideProps: GetServerSideProps<any> = async ({ req, query: { hashtag } }) => {+  const limit = 2;

Solche Konstanten würden sich als env Variablen eignen, um das global einstellen zu können.

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import axios from 'axios';++export const deleteMumble = async (id: string, accessToken?: string) => {+  try {+    const response = await axios.delete(`${process.env.NEXT_PUBLIC_QWACKER_API_URL}/posts/${id}`, {+      headers: {+        Authorization: accessToken ? `Bearer ${accessToken}` : null,+      },+    });++    if (!response) {+      throw new Error('Something was not okay');

Hier würde sich eine Custom Error Class anbieten:

class DeleteMumbleError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'DeleteMumbleError';
  }
}

So könntet ihr später den Fehler einfacher catchen.

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import { useEffect, useRef } from 'react';+import { useSession } from 'next-auth/react';+import useOnScreen from '@/hooks/useOnScreen';+import useSWR from 'swr';+import useSWRInfinite from 'swr/infinite';+import { alertService, deleteMumble } from '@/services';+import { FetchMumbles } from '@/types/fallback';+import { MumbleFetcher, SearchMumblesFetcher, StreamHook } from '@/types/swr';+import debounce from 'lodash.debounce';++export function useStream(+  url: string,+  limit: number,+  fallback: FetchMumbles,+  fetcher: MumbleFetcher | SearchMumblesFetcher,+  id?: string,+  hashtag?: string,+  creator?: { id: string }+) {+  const { data: session }: any = useSession();+  const ref = useRef<HTMLDivElement>(null);+  const [isOnScreen, setIsOnScreen] = useOnScreen(ref);++  const getKey = (pageIndex: number, previousPageData: FetchMumbles) => {+    if (previousPageData && !previousPageData.mumbles.length) {+      return null;+    }++    return {+      id,+      url,+      limit,+      offset: pageIndex * limit,+      token: session?.accessToken,+      tags: [hashtag],+      creator: creator?.id,+    };+  };++  const { data, mutate, size, setSize, error, isValidating, isLoading } = useSWRInfinite(getKey, fetcher, {+    fallbackData: [fallback],+    revalidateOnFocus: false,+    refreshInterval: 60000,+    // Hint: For better user experience, set this to true (more requests especially if small limit is set)+    revalidateAll: false,+    parallel: true,+  });++  const { data: newMumbles } = useSWR(+    id || hashtag || creator+      ? null+      : {+          url: '/api/mumbles',+          newerThanMumbleId: data && data[0]?.mumbles[0]?.id,+          limit,+          offset: 0,+          token: session?.accessToken,+        },+    fetcher,+    {+      revalidateOnFocus: false,+      refreshInterval: 10000,+    }+  );++  const handleIntersectionCallbackDebounced = debounce(async () => {+    setSize(size + 1);+    setIsOnScreen(false);+  }, 200);++  useEffect(() => {+    // TODO: id is needed on profile page, because there is no possibility for setting offset and limit on endpoint+    if (!id && isOnScreen && !isValidating && data && data.length * limit <= data[0].count)+      handleIntersectionCallbackDebounced();+  });++  const checkForNewMumbles = data && data[0]?.mumbles[0]?.id && newMumbles && newMumbles.count > 0;++  const quantityNewMumbles =+    data && data[0]?.mumbles[0]?.id && newMumbles && newMumbles.count === 1+      ? '1 neuer Mumble'+      : `${newMumbles && newMumbles.count} neue Mumbles`;++  const renderTimeline = !creator && !id;++  const handleDelete = async (id: string) => {+    if (!session?.accessToken) {+      alertService.error('Bitte melde dich an, sonst kannst du nicht löschen!!', {+        autoClose: true,+        keepAfterRouteChange: false,+      });+      return;+    }+    const res = await deleteMumble(id, session?.accessToken);++    //TODO: Is this a magic number, i don't think so.

Nein, ist der Status Code für "Sucess", aber "No Content": https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+// pages/_document.tsx+import React from 'react';+import Document, { Html, DocumentContext, NextScript } from 'next/document';+import { ServerStyleSheet } from 'styled-components';++export default class MyDocument extends Document {+  static async getInitialProps(ctx: DocumentContext) {+    const sheet = new ServerStyleSheet();+    const originalRenderPage = ctx.renderPage;+    try {+      ctx.renderPage = () =>+        originalRenderPage({+          enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />),+        });+      const initialProps = await Document.getInitialProps(ctx);++      return {+        ...initialProps,+        styles: [+          <React.Fragment key="styles">+            <Html lang="de">

Schaut hier: https://nextjs.org/docs/advanced-features/custom-document#customizing-renderpage

Das Html und seine untergeordneten Elemente sollten ausserhalb des stylesArrays sein. Diese können innerhalb der render Methode platziert werden:

  static async getInitialProps(ctx: DocumentContext) {
    const sheet = new ServerStyleSheet();
    const originalRenderPage = ctx.renderPage;
    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />),
        });
      const initialProps = await Document.getInitialProps(ctx);

      return {
        ...initialProps,
        styles: (
          <React.Fragment key="styles">
            {initialProps.styles}
            {sheet.getStyleElement()}
          </React.Fragment>
        ),
      };
    } finally {
      sheet.seal();
    }
  }

  render() {
    return (
      <Html lang="de">
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import React, { useState } from 'react';+import { GetServerSideProps, GetServerSidePropsContext } from 'next';+import { NextSeo } from 'next-seo';+import tw from 'twin.macro';+import Message from '../../../data/content.json';+import { getToken } from 'next-auth/jwt';+import { useSession } from 'next-auth/react';+import { fetchMyLikes, fetchMyMumbles, fetchUser, fetchUsers, QwackerUserResponse, User } from '@/services';+import { FetchMumbles } from '@/types/fallback';+import { Header, Stream } from '@/components';+import { Container, Switch } from '@smartive-education/design-system-component-library-yeahyeahyeah';++type HeaderProps = {+  creator: any;+  limit: number;+  fallbackUser: User;+  fallbackUsers: QwackerUserResponse;+  fallBackMyMumbles: FetchMumbles;+  fallBackMyLikes: FetchMumbles;+};++const ProfilePage = ({ creator, limit, fallbackUser, fallBackMyMumbles, fallBackMyLikes, fallbackUsers }: HeaderProps) => {+  const { data: session }: any = useSession();+  const [selection, setSelection] = useState('mumbles');++  const handleSelection = (value: string) => {+    setSelection(value);+  };++  return (+    <>+      <NextSeo+        title={`${session && session.user.firstname} ${session && session.user.lastname}'s mumble profile`}+        description={`Das Mumble-Profile von ${+          session && session.user.username+        }. Mumble, die Chat-App des CAS Frontend Engineer Advanced 2023.`}+        canonical={process.env.NEXT_PUBLIC_URL}+      />++      <Container layout="plain">+        <Header creator={creator} fallbackUser={fallbackUser} />++        {session?.user.id === creator.id ? (+          <>+            <SwitchContentWrapper>+              <Switch+                fCallBack={(value) => handleSelection(value)}+                options={[+                  {+                    label: `${Message.contents.switch.mumbles}`,+                    value: 'mumbles',+                  },+                  {+                    label: `${Message.contents.switch.likes}`,+                    value: 'likes',+                  },+                ]}+                value="mumbles"+              />+            </SwitchContentWrapper>++            <SelectionWrapper>+              {selection === 'mumbles' && (+                <Stream+                  url="/api/myMumbles"+                  limit={limit}+                  fallback={fallBackMyMumbles}+                  fallbackUsers={fallbackUsers}+                  fetcher={fetchMyMumbles}+                  creator={creator}+                />+              )}+              {selection === 'likes' && (+                <Stream+                  url="/api/myLikes"+                  // TODO: limit is set to 100 because we have to intercept the data (missing endpoint for likes in the API)+                  limit={100}+                  fallback={fallBackMyLikes}+                  fallbackUsers={fallbackUsers}+                  fetcher={fetchMyLikes}+                  creator={creator}+                />+              )}+            </SelectionWrapper>+          </>+        ) : (+          <SelectionWrapper>+            {selection === 'mumbles' && (+              <Stream+                url="/api/myMumbles"+                limit={limit}+                fallback={fallBackMyMumbles}+                fallbackUsers={fallbackUsers}+                fetcher={fetchMyMumbles}+                creator={creator}+              />+            )}+          </SelectionWrapper>+        )}+      </Container>+    </>+  );+};++export const getServerSideProps: GetServerSideProps<any> = async ({ req, query: { id } }: GetServerSidePropsContext) => {

Wenn ihr hier den Generic nutzt, bitte nicht any. Könnte gut getyped werden.

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import React from 'react';+import { GetServerSideProps } from 'next';+import Message from '../../../data/content.json';+import { getToken } from 'next-auth/jwt';+import { FetchMumbles } from '@/types/fallback';+import { QwackerUserResponse, fetchUsers, searchMumbles } from '@/services';+import { Stream } from '@/components/stream/Stream';+import { NextSeo } from 'next-seo';++const HashtagPage = ({+  limit,+  fallback,+  fallbackUsers,+  hashtag,+}: {+  limit: number;+  fallback: FetchMumbles;+  fallbackUsers: QwackerUserResponse;+  hashtag: string;+}) => {+  return (+    <>+      <NextSeo title={`${Message.seo.search.title}`} description={`${Message.seo.search.description}`} />+      <Stream+        url="/api/mumbles"+        limit={limit}+        fallback={fallback}+        fallbackUsers={fallbackUsers}+        hashtag={hashtag}+        fetcher={searchMumbles}+      />+    </>+  );+};+export const getServerSideProps: GetServerSideProps<any> = async ({ req, query: { hashtag } }) => {

Wieso hier nicht den Typ von oben (wieder)verwenden?

type HashtagPageProps = {
  limit: number;
  fallback: FetchMumbles;
  fallbackUsers: QwackerUserResponse;
  hashtag: string;
};
petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+// pages/_app.tsx+import React, { useEffect } from 'react';+import { SessionProvider } from 'next-auth/react';+import { AppProps } from 'next/app';+import GlobalStyles from '@/styles/GlobalStyles';+import { DefaultLayout } from '../layouts';+import { useRouter } from 'next/router';++const App = ({ Component, pageProps: { session, ...pageProps } }: AppProps) => {+  const router = useRouter();++  useEffect(() => {

Es wäre gut hier beispielsweise einen Kommentar hinzuzufügen: Was macht das und wieso wurde es genutzt:

  // Disable default scroll restoration behaviour to ...
petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import { useEffect, useRef } from 'react';+import { useSession } from 'next-auth/react';+import useOnScreen from '@/hooks/useOnScreen';+import useSWR from 'swr';+import useSWRInfinite from 'swr/infinite';+import { alertService, deleteMumble } from '@/services';+import { FetchMumbles } from '@/types/fallback';+import { MumbleFetcher, SearchMumblesFetcher, StreamHook } from '@/types/swr';+import debounce from 'lodash.debounce';++export function useStream(+  url: string,+  limit: number,+  fallback: FetchMumbles,+  fetcher: MumbleFetcher | SearchMumblesFetcher,+  id?: string,+  hashtag?: string,+  creator?: { id: string }+) {+  const { data: session }: any = useSession();+  const ref = useRef<HTMLDivElement>(null);+  const [isOnScreen, setIsOnScreen] = useOnScreen(ref);++  const getKey = (pageIndex: number, previousPageData: FetchMumbles) => {+    if (previousPageData && !previousPageData.mumbles.length) {+      return null;+    }++    return {+      id,+      url,+      limit,+      offset: pageIndex * limit,+      token: session?.accessToken,+      tags: [hashtag],+      creator: creator?.id,+    };+  };++  const { data, mutate, size, setSize, error, isValidating, isLoading } = useSWRInfinite(getKey, fetcher, {+    fallbackData: [fallback],+    revalidateOnFocus: false,+    refreshInterval: 60000,+    // Hint: For better user experience, set this to true (more requests especially if small limit is set)+    revalidateAll: false,+    parallel: true,+  });++  const { data: newMumbles } = useSWR(

Das ganze "newMumbles" Thema solltet ihr ebenfalls in eine eigene Komponente auslagern.

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import React from 'react';+import Link from 'next/link';+import useSWR from 'swr';+import { Hashtag as HashtagComponent } from '@smartive-education/design-system-component-library-yeahyeahyeah';+import { Mumble, searchMumbles } from '@/services';+import { useSession } from 'next-auth/react';++type HashtagSize = 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge';++type HashtagProps = {+  size: HashtagSize;+  hashtag: string;+};++export const Hashtag: React.FC<HashtagProps> = ({ size, hashtag }) => {+  const { data: session }: any = useSession();++  const { data: hashtagData } = useSWR(+    { url: '/api/mumbles', limit: 10, offset: 0, text: '#', token: session?.accessToken },+    searchMumbles,+    {+      refreshInterval: 10000,+    }+  );++  return <>{hashtagData && hashtagData.mumbles.map((mumble: Mumble) => renderHashtags(mumble.text, size, hashtag))}</>;+};++export const renderHashtags = (text: string, size: HashtagSize, hashtag?: string) => {+  const color = (str: string) => {+    if (hashtag) return str.replace('#', '') === hashtag ? 'violet' : 'slate-300';+    return 'violet';+  };++  return text.split(' ').map((str, i) => {

Persönliche Meinung str ist kein sprechenger Name, wäre nicht hashtagText oder textPart besser?

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

 /** @type {import('next').NextConfig} */ const nextConfig = {   reactStrictMode: true,+  transpilePackages: ['@acme/ui', 'lodash-es'],   swcMinify: true,-}+  images: {+    remotePatterns: [+      {+        protocol: 'https',+        hostname: 'storage.googleapis.com',+        port: '',+        pathname: '/qwacker-api-prod-data/**',+      },+    ],+    minimumCacheTTL: 1500000,+  },+  env: {+    NEXT_PUBLIC_URL: process.env.NEXT_PUBLIC_URL,+  },+}; -module.exports = nextConfig+module.exports = nextConfig;

Wird nachher überschrieben

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import React from 'react';+import Link from 'next/link';+import useSWR from 'swr';+import { Hashtag as HashtagComponent } from '@smartive-education/design-system-component-library-yeahyeahyeah';+import { Mumble, searchMumbles } from '@/services';+import { useSession } from 'next-auth/react';++type HashtagSize = 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge';++type HashtagProps = {+  size: HashtagSize;+  hashtag: string;+};++export const Hashtag: React.FC<HashtagProps> = ({ size, hashtag }) => {+  const { data: session }: any = useSession();++  const { data: hashtagData } = useSWR(+    { url: '/api/mumbles', limit: 10, offset: 0, text: '#', token: session?.accessToken },+    searchMumbles,+    {+      refreshInterval: 10000,+    }+  );++  return <>{hashtagData && hashtagData.mumbles.map((mumble: Mumble) => renderHashtags(mumble.text, size, hashtag))}</>;

Syntatic sugar:

return (
  <>
    {hashtagData &&
      hashtagData.mumbles.map(({ text }: Mumble) =>
        renderHashtags(text, size, hashtag)
      )}
  </>
);

Destructing ist bei so Themen hilfreich: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import React from 'react';+import Link from 'next/link';+import useSWR from 'swr';+import { Hashtag as HashtagComponent } from '@smartive-education/design-system-component-library-yeahyeahyeah';+import { Mumble, searchMumbles } from '@/services';+import { useSession } from 'next-auth/react';++type HashtagSize = 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge';++type HashtagProps = {+  size: HashtagSize;+  hashtag: string;+};++export const Hashtag: React.FC<HashtagProps> = ({ size, hashtag }) => {+  const { data: session }: any = useSession();++  const { data: hashtagData } = useSWR(+    { url: '/api/mumbles', limit: 10, offset: 0, text: '#', token: session?.accessToken },+    searchMumbles,+    {+      refreshInterval: 10000,+    }+  );++  return <>{hashtagData && hashtagData.mumbles.map((mumble: Mumble) => renderHashtags(mumble.text, size, hashtag))}</>;+};++export const renderHashtags = (text: string, size: HashtagSize, hashtag?: string) => {+  const color = (str: string) => {

const color = (str: string) => hashtag && str.replace("#", "") === hashtag ? "violet" : "slate-300"; (statt ein extra if)

petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import { StartScreen } from '@/components';++const LandingPage: React.FC = () => {

Syntatic sugar:

const LandingPage: React.FC = () => <StartScreen />;
petermanser

comment created time in a month

Pull request review commentsmartive-education/app-yeahyeahyeah

2023-04-25: Feedback zur Abgabe

+import tw from 'twin.macro';+import Message from '../../data/content.json';+import { Heading, Paragraph } from '@smartive-education/design-system-component-library-yeahyeahyeah';++const InternalServerError: React.FC = () => {+  return (+    <Container>+      <Heading+        label={`${Message.alerts.internalServerError.title}`}+        size="default"+        color="pink"+        tag="h3"+        alignment="center"+        mbSpacing="0"+      />+      <Paragraph size="large" alignment="center">+        {`${Message.alerts.internalServerError.text}`}

Auch hier keine Template literals: {Message.alerts.internalServerError.text}

petermanser

comment created time in a month

more