swr#mutate TypeScript Examples

The following examples show how to use swr#mutate. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Buttons.tsx    From frames with Mozilla Public License 2.0 7 votes vote down vote up
BackButton = ({response}: { response: { mediaId: number, episodeName: string | null, logo: string | null, name: string } }) => {
    const router = useRouter();
    const {disconnect} = useGroupWatch();

    const routeOut = async (ev: any) => {
        ev.stopPropagation();
        await disconnect();
        const url = '/' + (response.episodeName ? 'show' : 'movie') + '=' + response.name.replace(/\s/g, '+');
        document.body.removeAttribute('style');
        await router.push('/info?mediaId=' + response.mediaId, url);
        await mutate('/api/load/continue');
    }

    return (
        <svg className={styles.bb} viewBox="0 0 512 512" onClick={routeOut}>
            <path d="M256,0C114.844,0,0,114.844,0,256s114.844,256,256,256s256-114.844,256-256S397.156,0,256,0z M256,490.667
				C126.604,490.667,21.333,385.396,21.333,256S126.604,21.333,256,21.333S490.667,126.604,490.667,256S385.396,490.667,256,490.667
				z"/>
            <path d="M394.667,245.333H143.083l77.792-77.792c4.167-4.167,4.167-10.917,0-15.083c-4.167-4.167-10.917-4.167-15.083,0l-96,96
				c-4.167,4.167-4.167,10.917,0,15.083l96,96c2.083,2.083,4.813,3.125,7.542,3.125c2.729,0,5.458-1.042,7.542-3.125
				c4.167-4.167,4.167-10.917,0-15.083l-77.792-77.792h251.583c5.896,0,10.667-4.771,10.667-10.667S400.563,245.333,394.667,245.333
				z"/>
        </svg>
    )
}
Example #2
Source File: useQuestions.ts    From office-hours with GNU General Public License v3.0 6 votes vote down vote up
export function useQuestions(qid: number): UseQuestionReturn {
  const key = qid && `/api/v1/queues/${qid}/questions`;
  // Subscribe to sse
  const isLive = useEventSource(
    qid && `/api/v1/queues/${qid}/sse`,
    "question",
    useCallback(
      (data: SSEQueueResponse) => {
        if (data.questions) {
          mutate(
            key,
            plainToClass(ListQuestionsResponse, data.questions),
            false
          );
        }
      },
      [key]
    )
  );

  const {
    data: questions,
    error: questionsError,
    mutate: mutateQuestions,
  } = useSWR(key, async () => API.questions.index(Number(qid)), {
    refreshInterval: isLive ? 0 : 10 * 1000,
  });

  return { questions, questionsError, mutateQuestions };
}
Example #3
Source File: Buttons.tsx    From frames with Mozilla Public License 2.0 6 votes vote down vote up
Seen = ({id, seen}: ButtonInterfaces) => {
    const [hover, setHover] = useState(false);
    const [see, setSee] = useState(seen);

    async function seenHandler() {
        setSee(!see)
        await fetch(`/api/media/seen?id=${id}`);
        await mutate('/api/load/continue')
    }

    return (
        <button
            title={!see ? "seen ?" : "seen"}
            onClick={() => seenHandler()}
            className={(see && !hover) || (!see && hover) ? styles.roundGuys : `${styles.roundGuys} ${styles.noFill}`}
            onMouseEnter={() => setHover(true)}
            onMouseLeave={() => setHover(false)}
        >
            {(see && !hover) || (!see && hover) ? (
                <svg viewBox="0 0 426.667 426.667">
                    <g>
                        <path
                            d="M421.876,56.307c-6.548-6.78-17.352-6.968-24.132-0.42c-0.142,0.137-0.282,0.277-0.42,0.42L119.257,334.375
                                    l-90.334-90.334c-6.78-6.548-17.584-6.36-24.132,0.42c-6.388,6.614-6.388,17.099,0,23.713l102.4,102.4
                                    c6.665,6.663,17.468,6.663,24.132,0L421.456,80.44C428.236,73.891,428.424,63.087,421.876,56.307z"
                        />
                    </g>
                </svg>
            ) : (
                <svg viewBox="0 0 24 24" id="notSeenSvg">
                    <polyline points="9 11 12 14 22 4"/>
                    <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
                </svg>
            )}
        </button>
    );
}
Example #4
Source File: likebutton.tsx    From samuelkraft-next with MIT License 6 votes vote down vote up
LikeButton = ({ slug }: { slug: string }): JSX.Element | null => {
  const [mounted, setMounted] = useState(false)
  const { data } = useSWR(`/api/likes?slug=${slug}`, fetcher)
  const likes = data?.likes
  const liked = localStorage.getItem(slug) === 'true'

  useEffect(() => setMounted(true), [])

  const onLike = async () => {
    localStorage.setItem(slug, 'true')
    mutate(`/api/likes?slug=${slug}`, { ...data, likes: likes + 1 }, false)
    await fetch(`/api/likes?slug=${slug}`, { method: 'POST' })
  }

  if (!mounted) return null

  return (
    <Button disabled={liked} onClick={onLike} type="button" variant="like">
      <Heart className={liked ? styles.icon : ''} /> {typeof likes === 'undefined' ? '--' : likes}
    </Button>
  )
}
Example #5
Source File: Buttons.tsx    From frames with Mozilla Public License 2.0 6 votes vote down vote up
BackButton = ({response}: { response?: SpringPlay }) => {
    const router = useRouter();
    const {disconnect} = useGroupWatch();

    const routeOut = async (ev: any) => {
        ev.stopPropagation();
        await disconnect();
        if (response) {
            const url = '/' + (response.episodeName ? 'show' : 'movie') + '=' + response.name.replace(/\s/g, '+');
            document.body.removeAttribute('style');
            await router.push('/info?id=' + response.mediaId, url);

        } else await router.back();
        await mutate('/api/load/continue');
    }

    return (
        <svg className={styles.bb} viewBox="0 0 512 512" onClick={routeOut}>
            <path d="M256,0C114.844,0,0,114.844,0,256s114.844,256,256,256s256-114.844,256-256S397.156,0,256,0z M256,490.667
				C126.604,490.667,21.333,385.396,21.333,256S126.604,21.333,256,21.333S490.667,126.604,490.667,256S385.396,490.667,256,490.667
				z"/>
            <path d="M394.667,245.333H143.083l77.792-77.792c4.167-4.167,4.167-10.917,0-15.083c-4.167-4.167-10.917-4.167-15.083,0l-96,96
				c-4.167,4.167-4.167,10.917,0,15.083l96,96c2.083,2.083,4.813,3.125,7.542,3.125c2.729,0,5.458-1.042,7.542-3.125
				c4.167-4.167,4.167-10.917,0-15.083l-77.792-77.792h251.583c5.896,0,10.667-4.771,10.667-10.667S400.563,245.333,394.667,245.333
				z"/>
        </svg>
    )
}
Example #6
Source File: Buttons.tsx    From frames with Mozilla Public License 2.0 6 votes vote down vote up
Template = ({id, type, name, onClick, onHover}: ButtonInterfaces) => {
    if (name === 'see details')
        mutate('/api/load/continue');

    return (
        <button title={name} onMouseEnter={() => onHover ? onHover(true) : null}
                onMouseLeave={() => onHover ? onHover(false) : null}
                className={`${(id === 0 ? styles.playButton : id === 1 ? styles.trailerButton : styles.roundGuys)} ${type === 'add' ? '' : styles.noFill}`}
                onClick={onClick}>
            {type === 'down' ? <svg viewBox="0 0 24 24">
                <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
                <polyline points="7 10 12 15 17 10"/>
                <line x1="12" y1="15" x2="12" y2="3"/>
            </svg> : type === 'scan' ? <svg viewBox="0 0 24 24">
                <polyline points="23 4 23 10 17 10"/>
                <polyline points="1 20 1 14 7 14"/>
                <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
            </svg> : type === 'edit' ? <svg viewBox="0 0 24 24">
                <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
                <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
            </svg> : type === 'add' ? <svg viewBox="0 0 409.6 409.6">
                <g>
                    <path
                        d="M392.533,187.733H221.867V17.067C221.867,7.641,214.226,0,204.8,0s-17.067,7.641-17.067,17.067v170.667H17.067
                                C7.641,187.733,0,195.374,0,204.8s7.641,17.067,17.067,17.067h170.667v170.667c0,9.426,7.641,17.067,17.067,17.067
                                s17.067-7.641,17.067-17.067V221.867h170.667c9.426,0,17.067-7.641,17.067-17.067S401.959,187.733,392.533,187.733z"
                    />
                </g>
            </svg> : type === 'none' ? null : <svg viewBox="0 0 24 24">
                <circle cx="12" cy="12" r="10"/>
                <line x1="12" y1="8" x2="12" y2="12"/>
                <line x1="12" y1="16" x2="12.01" y2="16"/>
            </svg>}
            {id !== 2 && name}
        </button>
    )
}
Example #7
Source File: Api.ts    From takeout-app with MIT License 6 votes vote down vote up
function activateCandidateTrackCard(data: GetConferenceResponse) {
  console.log("Start activating candidate TrackCard");
  const now = dayjs().unix();
  let updated = false;
  for (const [, track] of Object.entries(data.conference.tracks)) {
    const candidate = track.card_candidate;
    if (!candidate) continue;
    if (now >= candidate.at) {
      console.log(`Activating candidate TrackCard for track=${track.slug}`, track.card_candidate);
      updated = true;
      track.card = candidate;
      track.card_candidate = null;
    } else {
      console.log(`Skipping candidate activation for track=${track.slug}`, track.card_candidate);
    }
  }
  if (updated) {
    console.log("Mutating /api/conference due to candidate TrackCard activation");
    mutate(API_CONFERENCE, { ...data }, false);
  }
}
Example #8
Source File: usePolicyPerformance.ts    From yasd with MIT License 6 votes vote down vote up
usePolicyPerformance = () => {
  const isSupported = useVersionSupport({
    ios: '4.9.5',
    macos: '4.2.4',
  })
  const { data, error } = useSWR<PolicyBenchmarkResults>(
    isSupported ? '/policies/benchmark_results' : null,
    fetcher,
    {
      revalidateOnFocus: false,
      revalidateOnReconnect: false,
      refreshInterval: 0,
    },
  )

  return {
    data,
    error,
    mutate: mutatePolicyPerformanceResults,
  }
}
Example #9
Source File: FirestoreEmulatedApiProvider.tsx    From firebase-tools-ui with Apache License 2.0 6 votes vote down vote up
export function useRecursiveDelete() {
  const { baseEmulatorUrl } = useFirestoreRestApi();
  const fetcher = useFetcher({
    method: 'DELETE',
  });

  return async (
    ref:
      | firebase.firestore.CollectionReference
      | firebase.firestore.DocumentReference
  ) => {
    mutate('*');
    const encodedPath = encodePath(ref.path);
    const url = `${baseEmulatorUrl}/documents/${encodedPath}`;
    return await fetcher(url);
  };
}
Example #10
Source File: FirestoreEmulatedApiProvider.tsx    From firebase-tools-ui with Apache License 2.0 6 votes vote down vote up
export function useEjector() {
  const { baseEmulatorUrl } = useFirestoreRestApi();
  const url = `${baseEmulatorUrl}/documents`;

  const fetcher = useFetcher({
    method: 'DELETE',
  });

  return async () => {
    mutate('*');
    return await fetcher(url);
  };
}
Example #11
Source File: ControlApi.tsx    From takeout-app with MIT License 5 votes vote down vote up
ControlApi = {
  useConference() {
    return useSWR<ControlGetConferenceResponse, ApiError>("/api/control/conference", swrFetcher, {
      revalidateOnFocus: false,
    });
  },

  async createControlSession(password: string) {
    const resp = await request("/api/session/take_control", "POST", null, {
      password,
    });
    mutate("/api/session");
    return resp.json();
  },

  useTrackCards(slug: TrackSlug) {
    return useSWR<ControlGetTrackCardsResponse, ApiError>(
      `/api/control/tracks/${encodeURIComponent(slug)}/cards`,
      swrFetcher,
    );
  },

  async createTrackCard(card: TrackCard) {
    const url = `/api/control/tracks/${encodeURIComponent(card.track)}/cards`;
    const resp = await request(url, "POST", null, {
      track_card: card,
    });
    mutate(url);
    return resp.json();
  },

  useAttendeeList(query: string | null) {
    return useSWR<ControlListAttendeesResponse, ApiError>(
      query !== null ? `/api/control/attendees?query=${encodeURIComponent(query)}` : null,
      swrFetcher,
      {
        revalidateOnFocus: false,
        revalidateOnReconnect: false,
      },
    );
  },

  useAttendee(id: number) {
    return useSWR<ControlGetAttendeeResponse, ApiError>(id ? `/api/control/attendees/${id}` : null, swrFetcher, {
      revalidateOnFocus: false,
      revalidateOnReconnect: false,
    });
  },

  async updateAttendee(id: number, params: ControlUpdateAttendeeRequestAttendee) {
    const url = `/api/control/attendees/${id}`;
    const resp = await request(url, "PUT", null, { attendee: params });
    mutate(`/api/control/attendees/${id}`);
    return resp.json();
  },
}
Example #12
Source File: index.tsx    From yasd with MIT License 5 votes vote down vote up
Page: React.FC = () => {
  const { t } = useTranslation()
  const { data: modules, error: modulesError } = useSWR<Modules>(
    '/modules',
    fetcher,
  )
  const [isLoading, setIsLoading] = useState(false)

  const isChecked = (name: string): boolean => {
    return modules?.enabled.includes(name) === true
  }

  const toggle = useCallback(
    (name: string, newVal: boolean) => {
      setIsLoading(true)

      fetcher({
        url: '/modules',
        method: 'POST',
        data: {
          [name]: newVal,
        },
      })
        .then(() => {
          toast.success(t('common.success_interaction'))
          return mutate('/modules')
        })
        .catch((err) => {
          toast.success(t('common.failed_interaction'))
          console.error(err)
        })
        .finally(() => {
          setIsLoading(false)
        })
    },
    [setIsLoading, t],
  )

  return (
    <PageContainer>
      <PageTitle title={t('home.modules')} />

      <div tw="divide-y divide-gray-200">
        {modules &&
          modules.available.map((mod) => {
            return (
              <div key={mod} tw="flex items-center justify-between p-3">
                <div tw="truncate leading-normal text-gray-700">{mod}</div>
                <div tw="flex items-center">
                  <Toggle
                    noMargin
                    label=""
                    labelChecked="on"
                    labelUnchecked="off"
                    disabled={isLoading}
                    checked={isChecked(mod)}
                    onChange={() => toggle(mod, !isChecked(mod))}
                  />
                </div>
              </div>
            )
          })}
      </div>
    </PageContainer>
  )
}
Example #13
Source File: usePolicyPerformance.ts    From yasd with MIT License 5 votes vote down vote up
mutatePolicyPerformanceResults = () =>
  mutate('/policies/benchmark_results')
Example #14
Source File: Api.ts    From takeout-app with MIT License 5 votes vote down vote up
export function consumeIvsMetadata(metadata: IvsMetadata) {
  mutate(
    API_CONFERENCE,
    (known: GetConferenceResponse) => {
      known = JSON.parse(JSON.stringify(known));
      let updated = false;
      metadata.i?.forEach((item) => {
        console.log(item);
        if (item.c) {
          const cardUpdate = item.c;
          const cardKey = cardUpdate.candidate ? "card_candidate" : "card";

          if (cardUpdate.clear) {
            const track = known.conference.tracks[cardUpdate.clear];
            if (track?.[cardKey]) {
              console.log("Clearing card", { key: cardKey, cardUpdate });
              track[cardKey] = null;
              updated = true;
            }
          } else if (cardUpdate.card) {
            const track = known.conference.tracks[cardUpdate.card.track];
            if (track) {
              console.log("Updating card", { key: cardKey, cardUpdate });
              track[cardKey] = cardUpdate.card;
              updated = true;
            }
          }
        }

        if (item.p) {
          const presence = item.p;
          const track = known.conference.tracks[presence.track];
          if (track) {
            const was = track.presences[presence.kind]?.at ?? 0;
            console.log("Updating stream presence", { presence, was });
            track.presences[presence.kind] = presence;
            updated = true;
          }
        }

        if (item.n) {
          const track = known.conference.tracks[item.n.track];
          if (track) {
            console.log("Updating viewerCount", item.n);
            track.viewerCount = item.n;
            updated = true;
          }
        }
      });
      if (updated) known.requested_at = 0;
      return known;
    },
    false,
  );
}
Example #15
Source File: Api.ts    From takeout-app with MIT License 5 votes vote down vote up
export function consumeChatAdminControl(adminControl: ChatAdminControl) {
  console.log("consumeChatAdminControl", adminControl);
  if (adminControl.pin) {
    mutate(
      `/api/tracks/${encodeURIComponent(adminControl.pin.track)}/chat_message_pin`,
      { track: adminControl.pin.track, pin: adminControl.pin },
      false,
    );
  }
  if (adminControl.spotlights) {
    const spotlights = adminControl.spotlights;
    mutate(
      API_CONFERENCE,
      (known: GetConferenceResponse) => {
        known = JSON.parse(JSON.stringify(known));
        spotlights.forEach((spotlight) => {
          const track = known.conference.tracks[spotlight.track];
          if (!track) return;
          const idx = track.spotlights.findIndex((v) => v.id === spotlight.id);
          if (idx !== -1) {
            track.spotlights[idx] = spotlight;
          } else {
            track.spotlights.push(spotlight);
          }
        });
        return known;
      },
      false,
    );
  }
  if (adminControl.presences) {
    const presences = adminControl.presences;
    mutate(
      API_CONFERENCE,
      (known2: GetConferenceResponse) => {
        const known = JSON.parse(JSON.stringify(known2));
        presences.forEach((presence) => {
          const track = known.conference.tracks[presence.track];
          if (track) {
            const was = track.presences[presence.kind]?.at ?? 0;
            if (was < presence.at) {
              console.log("Updating stream presence (chat)", presence);
              track.presences[presence.kind] = presence;
            }
          }
        });
        return known;
      },
      false,
    );
  }
}
Example #16
Source File: Buttons.tsx    From frames with Mozilla Public License 2.0 5 votes vote down vote up
MyList = ({id, myList}: ButtonInterfaces) => {
    const [hover, setHover] = useState(false);
    const [list, setList] = useState(myList);

    useEffect(() => {
        setList(myList);
    }, [myList]);

    async function listHandler() {
        setList(!list);
        await fetch(`/api/media/addToList?mediaId=${id}`);
        await mutate('/api/load/myList');
    }

    return (
        <button
            className={styles.roundGuys}
            onMouseEnter={() => setHover(true)}
            onMouseLeave={() => setHover(false)}
            onClick={listHandler}
            title={list ? "remove" : "add to list"}
        >
            {list ? (
                !hover ? (
                    <svg viewBox="0 0 426.667 426.667">
                        <g>
                            <path
                                d="M421.876,56.307c-6.548-6.78-17.352-6.968-24.132-0.42c-0.142,0.137-0.282,0.277-0.42,0.42L119.257,334.375
                            l-90.334-90.334c-6.78-6.548-17.584-6.36-24.132,0.42c-6.388,6.614-6.388,17.099,0,23.713l102.4,102.4
                            c6.665,6.663,17.468,6.663,24.132,0L421.456,80.44C428.236,73.891,428.424,63.087,421.876,56.307z"
                            />
                        </g>
                    </svg>
                ) : (
                    <svg viewBox="0 0 409.806 409.806">
                        <g>
                            <path
                                d="M228.929,205.01L404.596,29.343c6.78-6.548,6.968-17.352,0.42-24.132c-6.548-6.78-17.352-6.968-24.132-0.42
                            c-0.142,0.137-0.282,0.277-0.42,0.42L204.796,180.878L29.129,5.21c-6.78-6.548-17.584-6.36-24.132,0.42
                            c-6.388,6.614-6.388,17.099,0,23.713L180.664,205.01L4.997,380.677c-6.663,6.664-6.663,17.468,0,24.132
                            c6.664,6.662,17.468,6.662,24.132,0l175.667-175.667l175.667,175.667c6.78,6.548,17.584,6.36,24.132-0.42
                            c6.387-6.614,6.387-17.099,0-23.712L228.929,205.01z"
                            />
                        </g>
                    </svg>
                )
            ) : (
                <svg viewBox="0 0 409.6 409.6">
                    <g>
                        <path
                            d="M392.533,187.733H221.867V17.067C221.867,7.641,214.226,0,204.8,0s-17.067,7.641-17.067,17.067v170.667H17.067
                                C7.641,187.733,0,195.374,0,204.8s7.641,17.067,17.067,17.067h170.667v170.667c0,9.426,7.641,17.067,17.067,17.067
                                s17.067-7.641,17.067-17.067V221.867h170.667c9.426,0,17.067-7.641,17.067-17.067S401.959,187.733,392.533,187.733z"
                        />
                    </g>
                </svg>
            )}
        </button>
    );
}
Example #17
Source File: useQueue.ts    From office-hours with GNU General Public License v3.0 5 votes vote down vote up
/**
 * Get data for a queue.
 * @param qid Queue ID to get data for
 * @param onUpdate Optional callback to listen for when data is refetched, whether via HTTP or SSE
 */
export function useQueue(qid: number, onUpdate?: OnUpdate): UseQueueReturn {
  const key = qid && `/api/v1/queues/${qid}`;
  if (!(key in REFRESH_INFO)) {
    REFRESH_INFO[key] = {
      lastUpdated: null,
      onUpdates: new Set(),
    };
  }

  // Register onUpdate callback
  useEffect(() => {
    if (onUpdate) {
      const refreshInfo = REFRESH_INFO[key];
      refreshInfo.onUpdates.add(onUpdate);
      onUpdate(refreshInfo.lastUpdated);
      return () => {
        refreshInfo.onUpdates.delete(onUpdate);
      };
    }
  }, [onUpdate, key]);

  const isLive = useEventSource(
    qid && `/api/v1/queues/${qid}/sse`,
    "queue",
    useCallback(
      (data: SSEQueueResponse) => {
        if (data.queue) {
          mutate(key, plainToClass(GetQueueResponse, data.queue), false);
          REFRESH_INFO[key].lastUpdated = new Date();
          callOnUpdates(key);
        }
      },
      [key]
    )
  );

  const { data: queue, error: queueError, mutate: mutateQueue } = useSWR(
    key,
    useCallback(async () => API.queues.get(Number(qid)), [qid]),
    {
      refreshInterval: isLive ? 0 : 10 * 1000,
      onSuccess: (_, key) => {
        REFRESH_INFO[key].lastUpdated = new Date();
        callOnUpdates(key);
      },
    }
  );

  return {
    queue,
    queueError,
    mutateQueue,
    isLive,
  };
}
Example #18
Source File: Buttons.tsx    From frames with Mozilla Public License 2.0 5 votes vote down vote up
Seen = ({id, seen}: ButtonInterfaces) => {
    const [hover, setHover] = useState(false);
    const [see, setSee] = useState(seen);

    useEffect(() => {
        setSee(seen);
    }, [seen]);

    async function seenHandler() {
        setSee(!see)
        await fetch(`/api/media/seen?mediaId=${id}`);
        await mutate('/api/load/continue')
    }

    return (
        <button
            title={!see ? "seen ?" : "seen"}
            onClick={() => seenHandler()}
            className={(see && !hover) || (!see && hover) ? styles.roundGuys : `${styles.roundGuys} ${styles.noFill}`}
            onMouseEnter={() => setHover(true)}
            onMouseLeave={() => setHover(false)}
        >
            {(see && !hover) || (!see && hover) ? (
                <svg viewBox="0 0 426.667 426.667">
                    <g>
                        <path
                            d="M421.876,56.307c-6.548-6.78-17.352-6.968-24.132-0.42c-0.142,0.137-0.282,0.277-0.42,0.42L119.257,334.375
                                    l-90.334-90.334c-6.78-6.548-17.584-6.36-24.132,0.42c-6.388,6.614-6.388,17.099,0,23.713l102.4,102.4
                                    c6.665,6.663,17.468,6.663,24.132,0L421.456,80.44C428.236,73.891,428.424,63.087,421.876,56.307z"
                        />
                    </g>
                </svg>
            ) : (
                <svg viewBox="0 0 24 24" id="notSeenSvg">
                    <polyline points="9 11 12 14 22 4"/>
                    <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
                </svg>
            )}
        </button>
    );
}
Example #19
Source File: Buttons.tsx    From frames with Mozilla Public License 2.0 5 votes vote down vote up
Template = ({id, type, name, onClick, onHover}: ButtonInterfaces) => {
    if (name === 'see details')
        mutate('/api/load/continue');

    return (
        <button title={name} onMouseEnter={() => onHover ? onHover(true) : null}
                onMouseLeave={() => onHover ? onHover(false) : null}
                className={`${(id === 0 ? styles.playButton : id === 1 ? styles.trailerButton : styles.roundGuys)} ${type === 'add' || type === 'play' ? '' : styles.noFill}`}
                onClick={onClick}>
            {type === 'down' && <svg viewBox="0 0 24 24">
                <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
                <polyline points="7 10 12 15 17 10"/>
                <line x1="12" y1="15" x2="12" y2="3"/>
            </svg>}
            {type === 'scan' && <svg viewBox="0 0 24 24">
                <polyline points="23 4 23 10 17 10"/>
                <polyline points="1 20 1 14 7 14"/>
                <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
            </svg>}
            {type === 'edit' && <svg viewBox="0 0 24 24">
                <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
                <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
            </svg>}
            {type === 'add' && <svg viewBox="0 0 409.6 409.6">
                <g>
                    <path
                        d="M392.533,187.733H221.867V17.067C221.867,7.641,214.226,0,204.8,0s-17.067,7.641-17.067,17.067v170.667H17.067
                                C7.641,187.733,0,195.374,0,204.8s7.641,17.067,17.067,17.067h170.667v170.667c0,9.426,7.641,17.067,17.067,17.067
                                s17.067-7.641,17.067-17.067V221.867h170.667c9.426,0,17.067-7.641,17.067-17.067S401.959,187.733,392.533,187.733z"
                    />
                </g>
            </svg>}
            {type === 'play' && <svg viewBox="0 0 494.148 494.148">
                <g>
                    <path
                        d="M405.284,201.188L130.804,13.28C118.128,4.596,105.356,0,94.74,0C74.216,0,61.52,16.472,61.52,44.044v406.124
            c0,27.54,12.68,43.98,33.156,43.98c10.632,0,23.2-4.6,35.904-13.308l274.608-187.904c17.66-12.104,27.44-28.392,27.44-45.884
            C432.632,229.572,422.964,213.288,405.284,201.188z"
                        data-original="#000000"
                        className="active-path"
                        data-old_color="#000000"
                    />
                </g>
            </svg>}
            {type === 'info' && <svg viewBox="0 0 24 24">
                <circle cx="12" cy="12" r="10"/>
                <line x1="12" y1="8" x2="12" y2="12"/>
                <line x1="12" y1="16" x2="12.01" y2="16"/>
            </svg>}
            {id !== 2 && name}
        </button>
    )
}
Example #20
Source File: EmulatorConfigProvider.tsx    From firebase-tools-ui with Apache License 2.0 5 votes vote down vote up
EmulatorConfigProvider: React.FC<
  React.PropsWithChildren<{ refreshInterval: number }>
> = ({ refreshInterval, children }) => {
  // We don't use suspense here since the provider should never be suspended --
  // it merely creates a context for hooks (e.g. useConfig) who do suspend.
  const { data } = useSwr<Config | null>(CONFIG_API, configFetcher, {
    // Keep refreshing config to detect when emulators are stopped or restarted
    // with a different config (e.g. different emulators or host / ports).
    refreshInterval,

    // Emulator UI works fully offline. Even if the browser cannot reach LAN or
    // any router, this page and the CONFIG_API will still work if they are on
    // the same physical device (e.g. localhost/127.0.0.1/containers/VMs). See:
    // https://developer.mozilla.org/en-US/docs/Web/API/NavigatorOnLine/onLine
    refreshWhenOffline: true,
    onErrorRetry(_, __, ___, revalidate, { retryCount }) {
      if (retryCount < 2) {
        // If the Emulator Hub is running locally, we are very unlikely to get
        // any errors at all. However, an occasional blip may happen when using
        // Emulator UI remotely (or through port forwarding). Let's retry once.
        // Note that swr will keep `data` while retrying, so UI won't unload.
        // (This overwrites swr's built-in exponential backoff timeouts.)
        setTimeout(() => revalidate({ retryCount }), refreshInterval);
      } else {
        // We cannot reach the discovery API for 2+ times. This usually means
        // Emulator Hub is stopped or unreachable. swr won't clear data on error
        // so we manually set data to null to represent this situation.
        // Tell swr to revalidate (retry the fetch). No need for setTimeout.
        mutate(CONFIG_API, null, /* shouldRevalidate= */ true);
      }
    },
  });

  const promise = useOnChangePromise(data);

  return (
    <emulatorConfigContext.Provider
      value={{
        config: data,
        promise,
      }}
    >
      {children}
    </emulatorConfigContext.Provider>
  );
}
Example #21
Source File: Buttons.tsx    From frames with Mozilla Public License 2.0 5 votes vote down vote up
MyList = ({id, myList}: ButtonInterfaces) => {
    const [hover, setHover] = useState(false);
    const [list, setList] = useState(myList);

    async function listHandler() {
        setList(!list);
        await fetch(`/api/media/list?id=${id}`);
        await mutate('/api/load/myList');
    }

    return (
        <button
            className={styles.roundGuys}
            onMouseEnter={() => setHover(true)}
            onMouseLeave={() => setHover(false)}
            onClick={listHandler}
            title={list ? "remove" : "add to list"}
        >
            {list ? (
                !hover ? (
                    <svg viewBox="0 0 426.667 426.667">
                        <g>
                            <path
                                d="M421.876,56.307c-6.548-6.78-17.352-6.968-24.132-0.42c-0.142,0.137-0.282,0.277-0.42,0.42L119.257,334.375
                            l-90.334-90.334c-6.78-6.548-17.584-6.36-24.132,0.42c-6.388,6.614-6.388,17.099,0,23.713l102.4,102.4
                            c6.665,6.663,17.468,6.663,24.132,0L421.456,80.44C428.236,73.891,428.424,63.087,421.876,56.307z"
                            />
                        </g>
                    </svg>
                ) : (
                    <svg viewBox="0 0 409.806 409.806">
                        <g>
                            <path
                                d="M228.929,205.01L404.596,29.343c6.78-6.548,6.968-17.352,0.42-24.132c-6.548-6.78-17.352-6.968-24.132-0.42
                            c-0.142,0.137-0.282,0.277-0.42,0.42L204.796,180.878L29.129,5.21c-6.78-6.548-17.584-6.36-24.132,0.42
                            c-6.388,6.614-6.388,17.099,0,23.713L180.664,205.01L4.997,380.677c-6.663,6.664-6.663,17.468,0,24.132
                            c6.664,6.662,17.468,6.662,24.132,0l175.667-175.667l175.667,175.667c6.78,6.548,17.584,6.36,24.132-0.42
                            c6.387-6.614,6.387-17.099,0-23.712L228.929,205.01z"
                            />
                        </g>
                    </svg>
                )
            ) : (
                <svg viewBox="0 0 409.6 409.6">
                    <g>
                        <path
                            d="M392.533,187.733H221.867V17.067C221.867,7.641,214.226,0,204.8,0s-17.067,7.641-17.067,17.067v170.667H17.067
                                C7.641,187.733,0,195.374,0,204.8s7.641,17.067,17.067,17.067h170.667v170.667c0,9.426,7.641,17.067,17.067,17.067
                                s17.067-7.641,17.067-17.067V221.867h170.667c9.426,0,17.067-7.641,17.067-17.067S401.959,187.733,392.533,187.733z"
                        />
                    </g>
                </svg>
            )}
        </button>
    );
}
Example #22
Source File: CapabilityTile.tsx    From yasd with MIT License 5 votes vote down vote up
CapabilityTile: React.FC<CapabilityTileProps> = ({
  api,
  title,
  link,
}) => {
  const { t } = useTranslation()
  const profile = useProfile()
  const { data: capability } = useSWR<Capability>(
    profile !== undefined ? api : null,
    fetcher,
  )
  const history = useHistory()

  const toggle: ChangeEventHandler<HTMLButtonElement> = useCallback(
    (e) => {
      e.stopPropagation()
      e.preventDefault()

      fetcher({
        method: 'POST',
        url: api,
        data: {
          enabled: !capability?.enabled,
        },
      })
        .then(() => {
          return mutate(api)
        })
        .catch((err) => {
          console.error(err)
        })
    },
    [api, capability],
  )

  return (
    <MenuTile onClick={link ? () => history.push(link) : undefined}>
      <MenuTileTitle title={t(`home.${title}`)} />

      <MenuTileContent css={[tw`flex justify-end`]}>
        <Toggle
          noMargin
          label=""
          labelChecked={t('common.on')}
          labelUnchecked={t('common.off')}
          checked={capability?.enabled}
          onChange={toggle}
        />
      </MenuTileContent>
    </MenuTile>
  )
}
Example #23
Source File: EditPiggybankModal.tsx    From coindrop with GNU General Public License v3.0 4 votes vote down vote up
EditPiggybankModal: FunctionComponent<Props> = ({ isOpen, onClose }) => {
    const [isSubmitting, setIsSubmitting] = useState(false);
    const { colors } = useTheme();
    const { user } = useUser();
    const { colorMode } = useColorMode();
    const accentColorLevelInitial = getAccentColorLevelInitial(colorMode);
    const accentColorLevelHover = getAccentColorLevelHover(colorMode);
    const { push: routerPush, query: { piggybankName } } = useRouter();
    const initialPiggybankId = Array.isArray(piggybankName) ? piggybankName[0] : piggybankName;
    const { piggybankDbData } = useContext(PublicPiggybankDataContext);
    const { avatar_storage_id: currentAvatarStorageId } = piggybankDbData;
    const initialPaymentMethodsDataFieldArray = convertPaymentMethodsDataToFieldArray(piggybankDbData.paymentMethods);
    const initialAccentColor = piggybankDbData.accentColor ?? 'orange';
    const {
        register,
        handleSubmit,
        setValue,
        watch,
        control,
        formState: { isDirty },
    } = useForm({
        defaultValues: {
            piggybankId: initialPiggybankId,
            accentColor: initialAccentColor,
            website: piggybankDbData.website ?? '',
            name: piggybankDbData.name ?? '',
            verb: piggybankDbData.verb ?? 'pay',
            paymentMethods: sortByIsPreferredThenAlphabetical(initialPaymentMethodsDataFieldArray),
        },
    });
    const paymentMethodsFieldArrayName = "paymentMethods";
    const { fields, append, remove } = useFieldArray({
        control,
        name: paymentMethodsFieldArrayName,
    });
    const {
        accentColor: watchedAccentColor,
        piggybankId: watchedPiggybankId,
    } = watch(["accentColor", "piggybankId"]);
    const isAccentColorDirty = initialAccentColor !== watchedAccentColor;
    const isUrlUnchanged = initialPiggybankId === watchedPiggybankId;
    const { isPiggybankIdAvailable, setIsAddressTouched } = useContext(AdditionalValidation);
    const onSubmit = async (formData) => {
        try {
            setIsSubmitting(true);
            const dataToSubmit = {
                ...formData,
                paymentMethods: convertPaymentMethodsFieldArrayToDbMap(formData.paymentMethods ?? []),
                owner_uid: user.id,
                avatar_storage_id: currentAvatarStorageId ?? null,
            };
            if (isUrlUnchanged) {
                await db.collection('piggybanks').doc(initialPiggybankId).set(dataToSubmit);
                mutate(['publicPiggybankData', initialPiggybankId], dataToSubmit);
            } else {
                await axios.post(
                    '/api/createPiggybank',
                    {
                        oldPiggybankName: initialPiggybankId,
                        newPiggybankName: formData.piggybankId,
                        piggybankData: dataToSubmit,
                    },
                    {
                        headers: {
                            token: user.token,
                        },
                    },
                );
                try {
                    await db.collection('piggybanks').doc(initialPiggybankId).delete();
                } catch (err) {
                    console.log('error deleting old Coindrop page');
                }
                routerPush(`/${formData.piggybankId}`);
            }
            fetch(`/${initialPiggybankId}`, { headers: { isToForceStaticRegeneration: "true" }});
            onClose();
        } catch (error) {
            setIsSubmitting(false);
            // TODO: handle errors
            throw error;
        }
    };
    const handleAccentColorChange = (e) => {
        e.preventDefault();
        setValue("accentColor", e.target.dataset.colorname);
    };
    useEffect(() => {
        register("accentColor");
    }, [register]);
    const formControlTopMargin = 2;
    return (
        <Modal
            isOpen={isOpen}
            onClose={onClose}
            size="xl"
            closeOnOverlayClick={false}
        >
            <ModalOverlay />
            <ModalContent>
                <ModalHeader>Configure</ModalHeader>
                <ModalCloseButton />
                <form id="configure-coindrop-form" onSubmit={handleSubmit(onSubmit)}>
                    <ModalBody>
                        <AvatarInput />
                        <FormControl
                            isRequired
                            mt={formControlTopMargin}
                        >
                            <FormLabel htmlFor="input-piggybankId">URL</FormLabel>
                            <EditUrlInput
                                register={register}
                                value={watchedPiggybankId}
                            />
                        </FormControl>
                        <FormControl
                            mt={formControlTopMargin}
                        >
                            <FormLabel
                                htmlFor="input-accentColor"
                            >
                                Theme
                            </FormLabel>
                            <Flex wrap="wrap" justify="center">
                                {themeColorOptions.map(colorName => {
                                    const isColorSelected = watchedAccentColor === colorName;
                                    const accentColorInitial = colors[colorName][accentColorLevelInitial];
                                    const accentColorHover = colors[colorName][accentColorLevelHover];
                                    return (
                                    <Box
                                        key={colorName}
                                        as="button"
                                        bg={isColorSelected ? accentColorHover : accentColorInitial}
                                        _hover={{
                                            bg: accentColorHover,
                                        }}
                                        w="36px"
                                        h="36px"
                                        borderRadius="50%"
                                        mx={1}
                                        my={1}
                                        onClick={handleAccentColorChange}
                                        data-colorname={colorName}
                                    >
                                        {isColorSelected && (
                                            <CheckIcon color="#FFF" />
                                        )}
                                    </Box>
                                    );
                                })}
                            </Flex>
                        </FormControl>
                        <FormControl
                            isRequired
                            mt={formControlTopMargin}
                        >
                            <FormLabel
                                htmlFor="input-name"
                            >
                                Name
                            </FormLabel>
                            <Input
                                id="input-name"
                                name="name"
                                ref={register}
                            />
                        </FormControl>
                        <FormControl
                            isRequired
                            mt={formControlTopMargin}
                        >
                            <FormLabel
                                htmlFor="input-verb"
                            >
                                Payment action name
                            </FormLabel>
                            <Select
                                id="input-verb"
                                name="verb"
                                ref={register}
                            >
                                <option value="pay">Pay</option>
                                <option value="donate to">Donate to</option>
                                <option value="support">Support</option>
                                <option value="tip">Tip</option>
                            </Select>
                        </FormControl>
                        <FormControl
                            mt={formControlTopMargin}
                        >
                            <FormLabel
                                htmlFor="input-website"
                            >
                                Website
                            </FormLabel>
                            <Input
                                id="input-website"
                                name="website"
                                ref={register}
                                placeholder="http://"
                                type="url"
                            />
                        </FormControl>
                        <FormControl
                            mt={formControlTopMargin}
                        >
                            <FormLabel
                                htmlFor="input-paymentmethods"
                            >
                                Payment Methods
                            </FormLabel>
                            <PaymentMethodsInput
                                fields={fields}
                                control={control}
                                register={register}
                                remove={remove}
                                append={append}
                                fieldArrayName={paymentMethodsFieldArrayName}
                            />
                        </FormControl>
                    </ModalBody>
                    <Flex
                        id="modal-footer"
                        justify="space-between"
                        m={6}
                    >
                        <DeleteButton
                            piggybankName={initialPiggybankId}
                        />
                        <Flex>
                            <Button
                                variant="ghost"
                                onClick={onClose}
                            >
                                Cancel
                            </Button>
                            <Button
                                id="save-configuration-btn"
                                colorScheme="green"
                                mx={1}
                                type="submit"
                                isLoading={isSubmitting}
                                loadingText="Saving"
                                isDisabled={
                                    (
                                        !isDirty
                                        && !isAccentColorDirty // controlled accentColor field is not showing up in formState.dirtyFields
                                    )
                                    || !isPiggybankIdAvailable
                                    || !initialPiggybankId
                                }
                                onClick={() => setIsAddressTouched(true)}
                            >
                                Save
                            </Button>
                        </Flex>
                    </Flex>
                </form>
            </ModalContent>
        </Modal>
    );
}
Example #24
Source File: StudentQueue.tsx    From office-hours with GNU General Public License v3.0 4 votes vote down vote up
export default function StudentQueue({
  qid,
  cid,
}: StudentQueueProps): ReactElement {
  const { queue } = useQueue(qid);
  const { questions, mutateQuestions } = useQuestions(qid);
  const { studentQuestion, studentQuestionIndex } = useStudentQuestion(qid);
  const [isFirstQuestion, setIsFirstQuestion] = useLocalStorage(
    "isFirstQuestion",
    true
  );
  const [showJoinPopconfirm, setShowJoinPopconfirm] = useState(false);
  const { deleteDraftQuestion } = useDraftQuestion();
  const [isJoining, setIsJoining] = useState(
    questions &&
      studentQuestion &&
      studentQuestion?.status !== OpenQuestionStatus.Queued
  );
  const [popupEditQuestion, setPopupEditQuestion] = useState(false);

  const router = useRouter();
  const editQuestionQueryParam = Boolean(router.query.edit_question as string);

  useEffect(() => {
    if (editQuestionQueryParam && studentQuestion) {
      mutate(`/api/v1/queues/${qid}/questions`);
      setPopupEditQuestion(true);
      router.push(`/course/${cid}/queue/${qid}`);
    }
  }, [editQuestionQueryParam, qid, studentQuestion]);

  const studentQuestionId = studentQuestion?.id;
  const studentQuestionStatus = studentQuestion?.status;
  const leaveQueue = useCallback(async () => {
    await API.questions.update(studentQuestionId, {
      status: ClosedQuestionStatus.ConfirmedDeleted,
    });

    setIsJoining(false);
    await mutateQuestions();
  }, [mutateQuestions, studentQuestionId]);

  const rejoinQueue = useCallback(async () => {
    await API.questions.update(studentQuestionId, {
      status: OpenQuestionStatus.PriorityQueued,
    });
    await mutateQuestions();
  }, [mutateQuestions, studentQuestionId]);

  const finishQuestion = useCallback(
    async (text: string, questionType: QuestionType, groupable: boolean) => {
      const updateStudent = {
        text,
        questionType,
        groupable,
        status:
          studentQuestionStatus === OpenQuestionStatus.Drafting
            ? OpenQuestionStatus.Queued
            : studentQuestionStatus,
      };

      const updatedQuestionFromStudent = await API.questions.update(
        studentQuestionId,
        updateStudent
      );

      const newQuestionsInQueue = questions?.queue?.map((question: Question) =>
        question.id === studentQuestionId
          ? updatedQuestionFromStudent
          : question
      );

      // questions are the old questions and newQuestionsInQueue are questions that've been added since.
      mutateQuestions({
        ...questions,
        yourQuestion: updatedQuestionFromStudent,
        queue: newQuestionsInQueue,
      });
    },
    [studentQuestionStatus, studentQuestionId, questions, mutateQuestions]
  );

  const joinQueueAfterDeletion = useCallback(async () => {
    await API.questions.update(studentQuestion?.id, {
      status: ClosedQuestionStatus.ConfirmedDeleted,
    });
    await mutateQuestions();
    const newQuestion = await API.questions.create({
      text: studentQuestion.text,
      questionType: studentQuestion?.questionType,
      queueId: qid,
      location: studentQuestion?.location,
      force: true,
      groupable: false,
    });
    await API.questions.update(newQuestion.id, {
      status: OpenQuestionStatus.Queued,
    });
    await mutateQuestions();
  }, [mutateQuestions, qid, studentQuestion]);

  const openEditModal = useCallback(async () => {
    mutate(`/api/v1/queues/${qid}/questions`);
    setPopupEditQuestion(true);
  }, [qid]);

  const closeEditModal = useCallback(() => {
    setPopupEditQuestion(false);
    setIsJoining(false);
  }, []);

  const leaveQueueAndClose = useCallback(() => {
    //delete draft when they leave the queue
    deleteDraftQuestion();
    leaveQueue();
    closeEditModal();
  }, [deleteDraftQuestion, leaveQueue, closeEditModal]);

  const joinQueueOpenModal = useCallback(
    async (force: boolean) => {
      try {
        const createdQuestion = await API.questions.create({
          queueId: Number(qid),
          text: "",
          force: force,
          questionType: null,
          groupable: false,
        });
        const newQuestionsInQueue = [...questions?.queue, createdQuestion];
        await mutateQuestions({ ...questions, queue: newQuestionsInQueue });
        setPopupEditQuestion(true);
        return true;
      } catch (e) {
        if (
          e.response?.data?.message?.includes(
            ERROR_MESSAGES.questionController.createQuestion.oneQuestionAtATime
          )
        ) {
          return false;
        }
        return true;
        // TODO: how should we handle error that happens for another reason?
      }
    },
    [mutateQuestions, qid, questions]
  );

  const finishQuestionAndClose = useCallback(
    (
      text: string,
      qt: QuestionType,
      groupable: true,
      router: Router,
      cid: number
    ) => {
      deleteDraftQuestion();
      finishQuestion(text, qt, groupable);
      closeEditModal();
      if (isFirstQuestion) {
        notification.warn({
          style: { cursor: "pointer" },
          message: "Enable Notifications",
          className: "hide-in-percy",
          description:
            "Turn on notifications for when it's almost your turn to get help.",
          placement: "bottomRight",
          duration: 0,
          onClick: () => {
            notification.destroy();
            setIsFirstQuestion(false);
            router.push(`/settings?cid=${cid}`);
          },
        });
      }
    },
    [
      deleteDraftQuestion,
      finishQuestion,
      closeEditModal,
      isFirstQuestion,
      setIsFirstQuestion,
    ]
  );

  useHotkeys(
    "shift+e",
    () => {
      if (studentQuestion) {
        openEditModal();
      }
    },
    [studentQuestion]
  );

  useHotkeys(
    "shift+n",
    () => {
      if (!studentQuestion && queue?.allowQuestions && !queue?.isDisabled) {
        joinQueueOpenModal(false).then((res) => setShowJoinPopconfirm(!res));
      }
    },
    [studentQuestion, queue]
  );

  if (queue && questions) {
    if (!queue.isOpen) {
      return <h1 style={{ marginTop: "50px" }}>The Queue is Closed!</h1>;
    }
    return (
      <>
        <Container>
          <CantFindModal
            visible={studentQuestion?.status === LimboQuestionStatus.CantFind}
            leaveQueue={leaveQueue}
            rejoinQueue={rejoinQueue}
          />
          <StudentRemovedFromQueueModal
            question={studentQuestion}
            leaveQueue={leaveQueue}
            joinQueue={joinQueueAfterDeletion}
          />
          <QueueInfoColumn
            queueId={qid}
            isStaff={false}
            buttons={
              !studentQuestion && (
                <Popconfirm
                  title={
                    <PopConfirmTitle>
                      You already have a question in a queue for this course, so
                      your previous question will be deleted in order to join
                      this queue. Do you want to continue?
                    </PopConfirmTitle>
                  }
                  onConfirm={() => joinQueueOpenModal(true)}
                  okText="Yes"
                  cancelText="No"
                  disabled
                  visible={showJoinPopconfirm}
                  onVisibleChange={setShowJoinPopconfirm}
                >
                  <JoinButton
                    type="primary"
                    disabled={!queue?.allowQuestions || queue?.isDisabled}
                    data-cy="join-queue-button"
                    onClick={async () =>
                      setShowJoinPopconfirm(!(await joinQueueOpenModal(false)))
                    }
                  >
                    Join Queue
                  </JoinButton>
                </Popconfirm>
              )
            }
          />
          <VerticalDivider />
          <QueueListContainer>
            {studentQuestion && (
              <>
                <StudentBanner
                  queueId={qid}
                  editQuestion={openEditModal}
                  leaveQueue={leaveQueue}
                />
                <div style={{ marginTop: "40px" }} />
              </>
            )}
            <QueueQuestions
              questions={questions?.queue}
              studentQuestion={studentQuestion}
            />
          </QueueListContainer>
        </Container>

        <QuestionForm
          visible={
            (questions && !studentQuestion && isJoining) ||
            // && studentQuestion.status !== QuestionStatusKeys.Drafting)
            popupEditQuestion
          }
          question={studentQuestion}
          leaveQueue={leaveQueueAndClose}
          finishQuestion={finishQuestionAndClose}
          position={studentQuestionIndex + 1}
          cancel={closeEditModal}
        />
      </>
    );
  } else {
    return <div />;
  }
}
Example #25
Source File: index.tsx    From gonear-name with The Unlicense 4 votes vote down vote up
Profile = () => {
  useTopScroll()
  const toMarket = useToMarket()
  const [tab, setTab] = useState<number>(0)
  const [disableRewards, setDisableRewards] = useState<boolean>(false)
  const { near }: { near: INearProps | null } = useContext(NearContext)
  let { accountId } = useParams<{ accountId: string | undefined }>();

  const { data: profile } = useSWR(
    ['get_profile', near?.signedAccountId, accountId],
    () => near?.api.get_profile(accountId || near?.signedAccountId)
  )

  const grabRewards = async () => {
    if (!near || !profile) return null
    setDisableRewards(true)
    lowRewards = true
    profile.profitTaken += profile.availableRewards
    profile.availableRewards = 0
    await mutate(['get_profile', near?.signedAccountId, accountId], profile, false)
    await near.api.collect_rewards()
  }

  if (!near || !profile) return null
  if (!accountId && near.signedAccountId) accountId = near.signedAccountId
  if (!accountId) {
    return <Redirect to="/" />
  }

  const { numAcquisitions, numBets, numClaims, numOffers, availableRewards, betsVolume, profitTaken, acquisitions, participation} = profile
  let lowRewards = profile && profile.availableRewards < 0.1

  return (
    <Container>
      <BackButton onClick={toMarket} />
      <Main>
        <Title>{ accountId }</Title>
        <Bread>
          <BreadItem>{numOffers} Offer{numOffers !== 1 && 's'}</BreadItem>
          <BreadDot />
          <BreadItem>{numBets} Bid{numBets !== 1 && 's'}</BreadItem>
          <BreadDot />
          <BreadItem>{numClaims} Claim{numClaims !== 1 && 's'}</BreadItem>
          <BreadDot />
          <BreadItem>{numAcquisitions} Aacquisition{numAcquisitions !== 1 && 's'}</BreadItem>
        </Bread>
        <Cards>
          <Card type="bag">
            <CardTitle>Bids volume:</CardTitle>
            <CardValue>
              <MoneyIcon />
              <Value>{betsVolume.toFixed(2)}</Value>
            </CardValue>
          </Card>
          <Card type="coin">
            <CardTitle>Profit taken</CardTitle>
            <CardValue>
              <MoneyIcon />
              <Value>{profitTaken.toFixed(2)}</Value>
            </CardValue>
          </Card>
          <Card type="cup">
            <CardTitle>Available rewards</CardTitle>
            <CardValue>
              <MoneyIcon />
              <Value>{availableRewards.toFixed(2)}</Value>
            </CardValue>
          </Card>
        </Cards>

        {accountId === near.signedAccountId && (
          <Collect>
            <DetailsButton disabled={lowRewards || disableRewards} onClick={grabRewards}>
              Collect Rewards
            </DetailsButton>
            {lowRewards && <LowRewards>
              Accumulate at least<Value>0.1</Value> <InlineMoneyIcon />&nbsp; to collect rewards
            </LowRewards>}
          </Collect>
        )}

        <TableHeaders>
          <TableHeader active={tab === 0} onClick={() => setTab(0)}>
            <TableTitle>Participating</TableTitle>
            {participation.length > 0 ? <TableIndex>{participation.length}</TableIndex> : ''}
          </TableHeader>
          <TableHeader active={tab === 1} onClick={() => setTab(1)}>
            <TableTitle>Successful claims</TableTitle>
            {acquisitions.length > 0 ? <TableIndex>{acquisitions.length}</TableIndex> : ''}
          </TableHeader>
        </TableHeaders>
        <ProfileTable list={tab === 0 ? participation : acquisitions} isAcquisition={tab === 1} />
      </Main>
    </Container>
  )
}
Example #26
Source File: Api.ts    From takeout-app with MIT License 4 votes vote down vote up
Api = {
  useSession() {
    return useSWR<GetSessionResponse, ApiError>("/api/session", swrFetcher, {
      revalidateOnFocus: false,
    });
  },

  useAppVersion() {
    return useSWR<GetAppVersionResponse, ApiError>("/api/app_version", swrFetcher, {
      revalidateOnFocus: true,
      revalidateOnReconnect: true,
      focusThrottleInterval: 60000,
      refreshInterval: 90 * 1000, // TODO:
    });
  },

  useConference() {
    // TODO: Error handling
    const swr = useSWR<GetConferenceResponse, ApiError>(API_CONFERENCE, swrFetcher, {
      revalidateOnFocus: true,
      revalidateOnReconnect: true,
      //focusThrottleInterval: 15 * 1000, // TODO:
      compare(knownData, newData) {
        if (!knownData || !newData) return false;

        try {
          mergeConferenceData(newData, knownData);
        } catch (e) {
          console.warn(e);
          throw e;
        }
        const res = dequal(newData, knownData);
        return false; //res;
      },
    });

    // Schedule candidate TrackCard activation
    const { data } = swr;
    useEffect(() => {
      if (!data) return;
      const earliestCandidateActivationAt = determineEarliestCandidateActivationAt(data);
      if (!earliestCandidateActivationAt) return;
      const timeout = (earliestCandidateActivationAt - dayjs().unix()) * 1000 + 500;
      console.log(
        `Scheduling candidate TrackCard activation; earliest will happen at ${dayjs(
          new Date(earliestCandidateActivationAt * 1000),
        ).toISOString()}, in ${timeout / 1000}s`,
      );

      const timer = setTimeout(() => activateCandidateTrackCard(data), timeout);
      return () => clearTimeout(timer);
    }, [data]);

    return swr;
  },

  // XXX: this is not an API
  useTrackStreamOptions(): TrackStreamOptionsState {
    const browserStateKey = "rk-takeout-app--TrackStreamOption";
    let options: TrackStreamOptions = { interpretation: false, caption: false, chat: true };

    const browserState = window.localStorage?.getItem(browserStateKey);
    if (browserState) {
      try {
        options = JSON.parse(browserState);
      } catch (e) {
        console.warn(e);
      }
      if (!options.hasOwnProperty("chat")) {
        options.chat = true;
      }
    } else {
      const acceptJapanese = navigator.languages.findIndex((v) => /^ja($|-)/.test(v)) !== -1;
      options.interpretation = !acceptJapanese;
    }

    const [state, setState] = useState(options);

    return [
      state,
      (x: TrackStreamOptions) => {
        try {
          window.localStorage?.setItem(browserStateKey, JSON.stringify(x));
        } catch (e) {
          console.warn(e);
        }
        setState(x);
      },
    ];
  },

  async createSession(email: string, reference: string): Promise<CreateSessionResponse> {
    const resp = await request("/api/session", "POST", null, {
      email,
      reference,
    });
    mutate("/api/session");
    return resp.json();
  },

  async updateAttendee(name: string, gravatar_email: string): Promise<UpdateAttendeeResponse> {
    const resp = await request("/api/attendee", "PUT", null, {
      name,
      gravatar_email,
    });
    mutate("/api/session");
    return resp.json();
  },

  useStream(slug: TrackSlug, interpretation: boolean) {
    return useSWR<GetStreamResponse, ApiError>(
      `/api/streams/${slug}?interpretation=${interpretation ? "1" : "0"}`,
      swrFetcher,
      {
        revalidateOnFocus: true,
        revalidateOnReconnect: true,
        focusThrottleInterval: 60 * 15 * 1000,
        compare(knownData, newData) {
          // Accept new data only if expired
          if (!knownData?.stream || !newData?.stream) return false;
          const now = dayjs().unix() + 180;

          return !(knownData.stream.expiry < newData.stream.expiry && knownData.stream.expiry <= now);
        },
      },
    );
  },

  useChatSession(attendeeId: number | undefined) {
    // attendeeId for cache buster
    return useSWR<GetChatSessionResponse, ApiError>(
      attendeeId ? `/api/chat_session?i=${attendeeId}&p=${CACHE_BUSTER}` : null,
      swrFetcher,
      {
        revalidateOnFocus: true,
        revalidateOnReconnect: true,
        focusThrottleInterval: 60 * 40 * 1000,
        refreshInterval: 60 * 80 * 1000,
        refreshWhenHidden: false,
        refreshWhenOffline: false,
      },
    );
  },

  useChatMessagePin(track: TrackSlug | undefined) {
    return useSWR<GetChatMessagePinResponse, ApiError>(
      track ? `/api/tracks/${encodeURIComponent(track)}/chat_message_pin` : null,
      swrFetcher,
      {
        revalidateOnFocus: true,
        revalidateOnReconnect: true,
        focusThrottleInterval: 60 * 1000,
      },
    );
  },

  async sendChatMessage(track: TrackSlug, message: string, asAdmin?: boolean) {
    const resp = await request(`/api/tracks/${encodeURIComponent(track)}/chat_messages`, "POST", null, {
      message,
      as_admin: !!asAdmin,
    });
    return resp.json();
  },

  async pinChatMessage(track: TrackSlug, chatMessage: ChatMessage | null) {
    const resp = await request(`/api/tracks/${encodeURIComponent(track)}/chat_admin_message_pin`, "PUT", null, {
      chat_message: chatMessage,
    });
    return resp.json();
  },

  async createCaptionChatMembership(track: TrackSlug) {
    const resp = await request(`/api/tracks/${encodeURIComponent(track)}/caption_chat_membership`, "POST", null, {});
    return resp.json();
  },

  async deleteCaptionChatMembership(track: TrackSlug) {
    const resp = await request(`/api/tracks/${encodeURIComponent(track)}/caption_chat_membership`, "DELETE", null, {});
    return resp.json();
  },

  useConferenceSponsorships() {
    return useSWR<GetConferenceSponsorshipsResponse, ApiError>(
      `/api/conference_sponsorships?p=${CACHE_BUSTER}`,
      swrFetcher,
    );
  },
}
Example #27
Source File: AdvancedSettingsTab.tsx    From staticshield with MIT License 4 votes vote down vote up
AdvancedSettingsTab: React.FC<{ siteData: HarperDBRecord }> = ({
  siteData,
}) => {
  const { setVisible, bindings } = useModal();
  const [disableDeleteButton, setDisableDeleteButton] = useState(true);
  const [siteNameDeletingInput, setSiteNameDeletingInput] = useState('');
  const [isBlocked, setIsBlocked] = useState(siteData.is_login_blocked);
  const [_, setToast] = useToasts();
  const router = useRouter();

  useEffect(() => {
    if (siteNameDeletingInput === siteData.site_name) {
      setDisableDeleteButton(false);
    } else {
      setDisableDeleteButton(true);
    }
  }, [siteData.site_name, siteNameDeletingInput]);

  return (
    <div>
      <Card className='!mt-10'>
        <Text className='text-xl font-bold'>Block logins</Text>
        <Text p>
          Block logins temporarily. Existing logged in users will lose access to
          website once after the <Code>Login expiration time </Code> finishes
        </Text>
        <Text>
          The <Code>login expiration time </Code> can be changed in{' '}
          <Code> General Settings Tab </Code>
        </Text>
        <span>
          Allow logins
          <Toggle
            size='large'
            className='mx-3'
            initialChecked={siteData?.is_login_blocked}
            onChange={(e) => {
              setIsBlocked(e.target.checked);
            }}
          />
          Block logins
        </span>
        <Card.Footer className='!bg-warmgray-100'>
          <div className='flex items-center justify-between w-full'>
            <div>
              <Text>Please use 48 characters at maximum</Text>
            </div>
            <div>
              <Button
                size='small'
                type='secondary'
                auto
                onClick={async () => {
                  console.log(isBlocked);
                  const res = await blockLogins(isBlocked, siteData?.id);
                  if (isBlocked && res.success) {
                    setToast({
                      text: 'Logins blocked successfully',
                      type: 'warning',
                    });
                  } else if (!isBlocked && res.success) {
                    setToast({
                      text: 'Logins enabled successfully',
                      type: 'success',
                    });
                  } else {
                    setToast({
                      text: 'An unexpected error occured',
                      type: 'error',
                    });
                  }
                  mutate(
                    '/api/get-site-from-site-id/?siteId=' + siteData?.id,
                    { ...siteData, is_login_blocked: isBlocked },
                    false
                  );
                }}>
                Save
              </Button>
            </div>
          </div>
        </Card.Footer>
      </Card>
      <Card className='!mt-10 !border !border-red-500'>
        <Text className='text-xl font-bold'>Delete site</Text>
        <Text p>
          <span className='text-red-500'>Permanently remove</span> this website
          and all of its contents from the StaticShield. This action is not
          reversible, so please continue with caution.
        </Text>
        <Text>
          Also do not forget to remove StaticShield&apos;s code from your
          website.
        </Text>
        <Card.Footer className='!bg-warmgray-100'>
          <div className='flex items-center justify-between w-full'>
            <div>
              <Text>This action is irreversible</Text>
            </div>
            <div>
              <Button
                size='small'
                type='error'
                auto
                onClick={() => setVisible(true)}>
                Delete permanantely
              </Button>
            </div>
          </div>
        </Card.Footer>
      </Card>
      <Modal {...bindings}>
        <Modal.Title>Delete site</Modal.Title>
        <Modal.Subtitle>
          This deletion process cannot be reversed
        </Modal.Subtitle>
        <Modal.Content>
          Enter <Code>{siteData.site_name}</Code> to continue
          <Input
            width='100%'
            className='my-3 mt-5'
            label='Name of site →'
            onChange={(e) => setSiteNameDeletingInput(e.target.value)}
            onPaste={(e) => e.preventDefault()}
          />
        </Modal.Content>
        <Modal.Action passive onClick={() => setVisible(false)}>
          Cancel
        </Modal.Action>
        <Modal.Action
          type='error'
          onClick={() => {
            deleteSite(siteData.id)
              .then(() => {
                router.replace('/dashboard/?mutate=1');
                setToast({
                  text: `Successfully deleted ${siteData?.site_name}`,
                  type: 'success',
                });
              })
              .catch(() =>
                setToast({ text: 'An unexpected error occured', type: 'error' })
              );
          }}
          disabled={disableDeleteButton}>
          Delete
        </Modal.Action>
      </Modal>
    </div>
  );
}
Example #28
Source File: validateAndUpdateSiteData.ts    From staticshield with MIT License 4 votes vote down vote up
export default async function validateAndUpdateSiteData(
  data: GeneralSiteSettingsFormValues,
  field:
    | 'site_name'
    | 'site_desc'
    | 'password'
    | 'expiration_days'
    | 'cap'
    | 'title'
    | 'logo_url',
  siteId: string,
  previousData: GeneralSiteSettingsFormValues
): Promise<{ success: boolean }> {
  if (!schema.safeParse(data).success) {
    return {
      success: false,
    };
  }
  // -------------------------------------------------------------
  console.log(data);
  if (field === 'site_name') {
    try {
      await axios.post('/api/site/update-name', {
        siteName: data.site_name,
        siteId: siteId,
      });
    } catch (_) {
      return {
        success: false,
      };
    }
    mutate(
      '/api/get-site-from-site-id/?siteId=' + siteId,
      { ...previousData, site_name: data.site_name },
      false
    );
    return {
      success: true,
    };
  }
  // -------------------------------------------------------------
  else if (field === 'site_desc') {
    try {
      await axios.post('/api/site/update-site-desc', {
        siteDesc: data.site_desc,
        siteId: siteId,
      });
    } catch (_) {
      return {
        success: false,
      };
    }
    mutate(
      '/api/get-site-from-site-id/?siteId=' + siteId,
      { ...previousData, site_desc: data.site_desc },
      false
    );
    return {
      success: true,
    };
  }
  // -------------------------------------------------------------
  else if (field === 'expiration_days') {
    try {
      await axios.post('/api/site/update-max-login-duration', {
        max_login_duration: data.expiration_days,
        siteId: siteId,
      });
    } catch (_) {
      return {
        success: false,
      };
    }
    mutate(
      '/api/get-site-from-site-id/?siteId=' + siteId,
      { ...previousData, expiration_days: data.expiration_days },
      false
    );
    return {
      success: true,
    };
  }
  // -------------------------------------------------------------
  else if (field === 'password') {
    if (data.password === 'A-str0ng-p@55w0rd') {
      return {
        success: false,
      };
    }

    try {
      await axios.post('/api/site/update-site-password', {
        password: data.password,
        siteId: siteId,
      });
    } catch (_) {
      return {
        success: false,
      };
    }
    mutate(
      '/api/get-site-from-site-id/?siteId=' + siteId,
      { ...previousData, password: data.password },
      false
    );
    return {
      success: true,
    };
  }
  // -------------------------------------------------------------
  else if (field === 'logo_url') {
    try {
      await axios.post('/api/site/update-logo-url', {
        logoUrl: data.logo_url,
        siteId: siteId,
      });
    } catch (_) {
      return {
        success: false,
      };
    }
    mutate(
      '/api/get-site-from-site-id/?siteId=' + siteId,
      { ...previousData, logo_url: data.logo_url },
      false
    );
    return {
      success: true,
    };
  }
  // -------------------------------------------------------------
  else if (field === 'cap') {
    try {
      await axios.post('/api/site/update-caption', {
        cap: data.cap,
        siteId: siteId,
      });
    } catch (_) {
      return {
        success: false,
      };
    }
    mutate(
      '/api/get-site-from-site-id/?siteId=' + siteId,
      { ...previousData, cap: data.cap },
      false
    );
    return {
      success: true,
    };
  } else if (field === 'title') {
    try {
      await axios.post('/api/site/update-title', {
        title: data.title,
        siteId: siteId,
      });
    } catch (_) {
      return {
        success: false,
      };
    }
    mutate(
      '/api/get-site-from-site-id/?siteId=' + siteId,
      { ...previousData, title: data.title },
      false
    );
    return {
      success: true,
    };
  }
  // -------------------------------------------------------------
}
Example #29
Source File: useEtherSWR.ts    From ether-swr with MIT License 4 votes vote down vote up
function useEtherSWR<Data = any, Error = any>(
  ...args: any[]
): SWRResponse<Data, Error> {
  let _key: ethKeyInterface
  let fn: any //fetcherFn<Data> | undefined
  let config: EthSWRConfigInterface<Data, Error> = { subscribe: [] }
  let isMulticall = false
  if (args.length >= 1) {
    _key = args[0]
    isMulticall = Array.isArray(_key[0])
  }
  if (args.length > 2) {
    fn = args[1]
    //FIXME we lost default value subscriber = []
    config = args[2]
  } else {
    if (typeof args[1] === 'function') {
      fn = args[1]
    } else if (typeof args[1] === 'object') {
      config = args[1]
    }
  }

  config = Object.assign({}, useContext(EthSWRConfigContext), config)

  if (fn === undefined) {
    fn = config.fetcher || etherJsFetcher(config.web3Provider, config.ABIs)
  }

  // TODO LS implement a getTarget and change subscribe interface {subscribe: {name: "Transfer", target: 0x01}}
  const [target] = isMulticall
    ? [_key[0][0]] // pick the first element of the list.
    : _key

  const { cache } = useSWRConfig()
  // we need to serialize the key as string otherwise
  // a new array is created everytime the component is rendered
  // we follow SWR format
  const normalizeKey = isMulticall ? JSON.stringify(_key) : _key

  // base methods (e.g. getBalance, getBlockNumber, etc)
  useEffect(() => {
    if (!config.web3Provider || !config.subscribe || Array.isArray(target)) {
      // console.log('skip')
      return () => ({})
    }
    // console.log('effect!')
    const contract = buildContract(target, config)

    const subscribers = Array.isArray(config.subscribe)
      ? config.subscribe
      : [config.subscribe]

    const instance: Contract | Provider = contract || config.web3Provider

    subscribers.forEach(subscribe => {
      let filter
      const internalKey = unstable_serialize(normalizeKey)
      if (typeof subscribe === 'string') {
        filter = subscribe
        instance.on(filter, () => {
          // console.log('on(string):', { filter }, Array.from(cache.keys()))
          mutate(internalKey, undefined, true)
        })
      } else if (typeof subscribe === 'object' && !Array.isArray(subscribe)) {
        const { name, topics, on } = subscribe
        const args = topics || []
        filter = contract ? contract.filters[name](...args) : name
        // console.log('subscribe:', filter)
        instance.on(filter, (...args) => {
          if (on) {
            // console.log('on(object):', { filter }, Array.from(cache.keys()))
            on(cache.get(internalKey), ...args)
          } else {
            // auto refresh
            // console.log('auto(refresh):', { filter }, Array.from(cache.keys()))
            mutate(internalKey, undefined, true)
          }
        })
      }
    })

    return () => {
      subscribers.forEach(filter => {
        instance.removeAllListeners(filter)
      })
    }
  }, [unstable_serialize(normalizeKey), target])

  return useSWR(normalizeKey, fn, config)
}