update widgets

This commit is contained in:
reya 2023-11-08 16:17:47 +07:00
parent 6b030f2902
commit 108ecafab7
7 changed files with 137 additions and 112 deletions

View File

@ -6,37 +6,9 @@ import { useStorage } from '@libs/storage/provider';
import { ArrowLeftIcon, CheckCircleIcon, LoaderIcon } from '@shared/icons'; import { ArrowLeftIcon, CheckCircleIcon, LoaderIcon } from '@shared/icons';
import { WidgetKinds } from '@stores/constants'; import { HASHTAGS, WidgetKinds } from '@stores/constants';
import { useOnboarding } from '@stores/onboarding'; import { useOnboarding } from '@stores/onboarding';
const data = [
{ hashtag: '#bitcoin' },
{ hashtag: '#nostr' },
{ hashtag: '#nostrdesign' },
{ hashtag: '#security' },
{ hashtag: '#zap' },
{ hashtag: '#LFG' },
{ hashtag: '#zapchain' },
{ hashtag: '#shitcoin' },
{ hashtag: '#plebchain' },
{ hashtag: '#nodes' },
{ hashtag: '#hodl' },
{ hashtag: '#stacksats' },
{ hashtag: '#nokyc' },
{ hashtag: '#meme' },
{ hashtag: '#memes' },
{ hashtag: '#memestr' },
{ hashtag: '#nostriches' },
{ hashtag: '#dev' },
{ hashtag: '#anime' },
{ hashtag: '#waifu' },
{ hashtag: '#manga' },
{ hashtag: '#lume' },
{ hashtag: '#snort' },
{ hashtag: '#damus' },
{ hashtag: '#primal' },
];
export function OnboardHashtagScreen() { export function OnboardHashtagScreen() {
const { db } = useStorage(); const { db } = useStorage();
@ -93,7 +65,7 @@ export function OnboardHashtagScreen() {
</h1> </h1>
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="flex h-[420px] w-full flex-col overflow-y-auto rounded-xl bg-neutral-100 dark:bg-neutral-900"> <div className="flex h-[420px] w-full flex-col overflow-y-auto rounded-xl bg-neutral-100 dark:bg-neutral-900">
{data.map((item: { hashtag: string }) => ( {HASHTAGS.map((item: { hashtag: string }) => (
<button <button
key={item.hashtag} key={item.hashtag}
type="button" type="button"

View File

@ -190,7 +190,7 @@ export const User = memo(function User({
if (status === 'pending') { if (status === 'pending') {
return ( return (
<div className="flex items-center gap-2.5"> <div className="flex items-center gap-2.5">
<div className="h-11 w-11 shrink-0 animate-pulse rounded-lg bg-neutral-300 dark:bg-neutral-700" /> <div className="h-10 w-10 shrink-0 animate-pulse rounded-lg bg-neutral-300 dark:bg-neutral-700" />
<div className="flex w-full flex-col items-start gap-1"> <div className="flex w-full flex-col items-start gap-1">
<div className="h-4 w-36 animate-pulse rounded bg-neutral-300 dark:bg-neutral-700" /> <div className="h-4 w-36 animate-pulse rounded bg-neutral-300 dark:bg-neutral-700" />
<div className="h-4 w-24 animate-pulse rounded bg-neutral-300 dark:bg-neutral-700" /> <div className="h-4 w-24 animate-pulse rounded bg-neutral-300 dark:bg-neutral-700" />
@ -201,19 +201,19 @@ export const User = memo(function User({
return ( return (
<div className="flex items-center gap-2.5"> <div className="flex items-center gap-2.5">
<Avatar.Root className="shrink-0"> <Avatar.Root className="h-10 w-10 shrink-0">
<Avatar.Image <Avatar.Image
src={user?.picture || user?.image} src={user?.picture || user?.image}
alt={pubkey} alt={pubkey}
loading="lazy" loading="lazy"
decoding="async" decoding="async"
className="h-11 w-11 rounded-lg" className="h-10 w-10 rounded-lg object-cover"
/> />
<Avatar.Fallback delayMs={300}> <Avatar.Fallback delayMs={300}>
<img <img
src={svgURI} src={svgURI}
alt={pubkey} alt={pubkey}
className="h-11 w-11 rounded-lg bg-black dark:bg-white" className="h-10 w-10 rounded-lg bg-black dark:bg-white"
/> />
</Avatar.Fallback> </Avatar.Fallback>
</Avatar.Root> </Avatar.Root>

View File

@ -34,7 +34,7 @@ export function TrendingNotesWidget({ params }: { params: Widget }) {
return ( return (
<WidgetWrapper> <WidgetWrapper>
<TitleBar id={params.id} title="Trending Notes" /> <TitleBar id={params.id} title="Trending Notes" />
<div className="flex-1"> <VList className="flex-1">
{status === 'pending' ? ( {status === 'pending' ? (
<div className="flex h-full w-full items-center justify-center "> <div className="flex h-full w-full items-center justify-center ">
<div className="inline-flex flex-col items-center justify-center gap-2"> <div className="inline-flex flex-col items-center justify-center gap-2">
@ -56,14 +56,9 @@ export function TrendingNotesWidget({ params }: { params: Widget }) {
</div> </div>
</div> </div>
) : ( ) : (
<VList className="h-full"> data.map((item) => <MemoizedTextNote key={item.event.id} event={item.event} />)
{data.map((item) => (
<MemoizedTextNote key={item.event.id} event={item.event} />
))}
<div className="h-16" />
</VList>
)} )}
</div> </VList>
</WidgetWrapper> </WidgetWrapper>
); );
} }

View File

@ -2,8 +2,9 @@ import { useState } from 'react';
import { useStorage } from '@libs/storage/provider'; import { useStorage } from '@libs/storage/provider';
import { ArrowRightCircleIcon, CheckCircleIcon } from '@shared/icons'; import { ArrowRightCircleIcon, CancelIcon, CheckCircleIcon } from '@shared/icons';
import { User } from '@shared/user'; import { User } from '@shared/user';
import { WidgetWrapper } from '@shared/widgets';
import { WidgetKinds } from '@stores/constants'; import { WidgetKinds } from '@stores/constants';
@ -25,10 +26,6 @@ export function XfeedsWidget({ params }: { params: Widget }) {
setGroups(arr); setGroups(arr);
}; };
const cancel = () => {
removeWidget.mutate(params.id);
};
const submit = async () => { const submit = async () => {
addWidget.mutate({ addWidget.mutate({
kind: WidgetKinds.local.feeds, kind: WidgetKinds.local.feeds,
@ -40,58 +37,60 @@ export function XfeedsWidget({ params }: { params: Widget }) {
}; };
return ( return (
<div className="flex h-full shrink-0 grow-0 basis-[400px] flex-col items-center justify-center"> <WidgetWrapper>
<div className="w-full px-5"> <div className="flex h-11 shrink-0 items-center justify-between px-3">
<h3 className="mb-4 text-center font-semibold text-neutral-900 dark:text-neutral-100"> <div className="w-6 shrink-0" />
Choose account you want to add to group feeds <h3 className="text-center font-semibold text-neutral-900 dark:text-neutral-100">
Adding group feeds
</h3> </h3>
<div className="mb-0 flex flex-col gap-2"> <button
<div> type="button"
<input onClick={() => removeWidget.mutate(params.id)}
value={title} className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded text-neutral-900 backdrop-blur-xl hover:bg-neutral-100 dark:text-neutral-100 dark:hover:bg-neutral-900"
onChange={(e) => setTitle(e.target.value)} >
placeholder="Title" <CancelIcon className="h-3 w-3" />
className="relative h-11 w-full rounded-lg bg-neutral-200 px-3 py-1 text-neutral-900 !outline-none placeholder:text-neutral-500 dark:bg-neutral-800 dark:text-neutral-100 dark:placeholder:text-neutral-300" </button>
/> </div>
</div> <div className="flex flex-1 flex-col justify-between">
<div className="flex h-[500px] w-full flex-col overflow-y-auto rounded-lg bg-neutral-200 py-2 scrollbar-none dark:bg-neutral-800"> <div className="flex min-h-0 flex-1 flex-col gap-2 px-3 pb-3">
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Group name"
className="relative h-11 w-full rounded-lg bg-neutral-100 px-3 py-1 text-neutral-900 !outline-none placeholder:text-neutral-500 dark:bg-neutral-900 dark:text-neutral-100 dark:placeholder:text-neutral-300"
/>
<div className="flex-grow-1 flex flex-1 shrink basis-0 flex-col overflow-y-auto rounded-xl bg-neutral-50 dark:bg-neutral-950">
<div className="flex h-10 shrink-0 items-center border-b border-neutral-100 px-4 text-sm font-semibold dark:border-neutral-900">
Add users {title ? 'to ' + title : ''}
</div>
{db.account.circles.map((item: string) => ( {db.account.circles.map((item: string) => (
<button <button
key={item} key={item}
type="button" type="button"
onClick={() => toggleGroup(item)} onClick={() => toggleGroup(item)}
className="inline-flex transform items-center justify-between px-4 py-2 hover:bg-neutral-300 dark:hover:bg-neutral-700" className="inline-flex transform items-center justify-between px-4 py-2 hover:bg-neutral-100 dark:hover:bg-neutral-900"
> >
<User pubkey={item} variant="simple" /> <User pubkey={item} variant="simple" />
{groups.includes(item) && ( {groups.includes(item) ? (
<div> <CheckCircleIcon className="h-5 w-5 text-teal-500" />
<CheckCircleIcon className="h-4 w-4 text-green-400" /> ) : null}
</div>
)}
</button> </button>
))} ))}
</div> </div>
<div className="flex flex-col items-center justify-center gap-2"> </div>
<button <div className="flex h-14 shrink-0 gap-2 border-t border-neutral-100 px-3 pt-2.5 dark:border-neutral-900">
type="submit" <button
disabled={groups.length < 1} type="submit"
onClick={submit} disabled={groups.length < 1}
className="inline-flex h-11 w-full items-center justify-between gap-2 rounded-lg bg-blue-500 px-6 font-medium leading-none text-white hover:bg-blue-600 focus:outline-none disabled:opacity-50" onClick={submit}
> className="inline-flex h-9 w-full items-center justify-between gap-2 rounded-lg bg-blue-500 px-6 font-medium text-white hover:bg-blue-600 focus:outline-none disabled:opacity-50"
<span className="w-5" /> >
<span>Add {groups.length} account to group feed</span> <span className="w-5" />
<ArrowRightCircleIcon className="h-5 w-5" /> <span>Add {groups.length} user to group feed</span>
</button> <ArrowRightCircleIcon className="h-5 w-5" />
<button </button>
type="button"
onClick={cancel}
className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-lg px-6 font-medium leading-none text-neutral-900 hover:bg-neutral-200 focus:outline-none disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-800"
>
Cancel
</button>
</div>
</div> </div>
</div> </div>
</div> </WidgetWrapper>
); );
} }

View File

@ -1,8 +1,9 @@
import { Resolver, useForm } from 'react-hook-form'; import { Resolver, useForm } from 'react-hook-form';
import { ArrowRightCircleIcon } from '@shared/icons'; import { ArrowRightCircleIcon, CancelIcon } from '@shared/icons';
import { WidgetWrapper } from '@shared/widgets';
import { WidgetKinds } from '@stores/constants'; import { HASHTAGS, WidgetKinds } from '@stores/constants';
import { useWidget } from '@utils/hooks/useWidget'; import { useWidget } from '@utils/hooks/useWidget';
import { Widget } from '@utils/types'; import { Widget } from '@utils/types';
@ -29,15 +30,12 @@ export function XhashtagWidget({ params }: { params: Widget }) {
const { addWidget, removeWidget } = useWidget(); const { addWidget, removeWidget } = useWidget();
const { const {
register, register,
setValue,
setError, setError,
handleSubmit, handleSubmit,
formState: { errors, isDirty, isValid }, formState: { errors, isDirty, isValid },
} = useForm<FormValues>({ resolver }); } = useForm<FormValues>({ resolver });
const cancel = () => {
removeWidget.mutate(params.id);
};
const onSubmit = async (data: FormValues) => { const onSubmit = async (data: FormValues) => {
try { try {
addWidget.mutate({ addWidget.mutate({
@ -56,42 +54,57 @@ export function XhashtagWidget({ params }: { params: Widget }) {
}; };
return ( return (
<div className="flex h-full shrink-0 grow-0 basis-[400px] flex-col items-center justify-center"> <WidgetWrapper>
<div className="w-full px-5"> <div className="flex h-11 shrink-0 items-center justify-between px-3">
<h3 className="mb-4 text-center text-lg font-semibold"> <div className="w-6 shrink-0" />
Enter hashtag you want to follow <h3 className="text-center font-semibold text-neutral-900 dark:text-neutral-100">
Adding hashtag feeds
</h3> </h3>
<button
type="button"
onClick={() => removeWidget.mutate(params.id)}
className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded text-neutral-900 backdrop-blur-xl hover:bg-neutral-100 dark:text-neutral-100 dark:hover:bg-neutral-900"
>
<CancelIcon className="h-3 w-3" />
</button>
</div>
<div className="flex flex-1 flex-col px-3">
<form onSubmit={handleSubmit(onSubmit)} className="mb-0 flex flex-col gap-2"> <form onSubmit={handleSubmit(onSubmit)} className="mb-0 flex flex-col gap-2">
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<input <input
{...register('hashtag', { required: true, minLength: 1 })} {...register('hashtag', { required: true, minLength: 1 })}
placeholder="#bitcoin" placeholder="Enter a hashtag"
className="relative h-12 w-full rounded-lg bg-neutral-200 px-3 py-1 text-neutral-900 !outline-none placeholder:text-neutral-500 dark:bg-neutral-800 dark:text-neutral-100 dark:placeholder:text-neutral-300" className="relative h-11 w-full rounded-lg bg-neutral-100 px-3 py-1 text-neutral-900 !outline-none placeholder:text-neutral-500 dark:bg-neutral-900 dark:text-neutral-100 dark:placeholder:text-neutral-300"
/> />
<span className="text-sm text-red-400"> <span className="text-sm text-red-400">
{errors.hashtag && <p>{errors.hashtag.message}</p>} {errors.hashtag && <p>{errors.hashtag.message}</p>}
</span> </span>
</div> </div>
<div className="flex flex-col items-center justify-center gap-2"> <div className="flex flex-wrap items-center justify-start gap-2">
{HASHTAGS.map((item) => (
<button
key={item.hashtag}
type="button"
onClick={() => setValue('hashtag', item.hashtag)}
className="inline-flex h-6 w-min items-center justify-center rounded-md bg-neutral-100 px-2 text-sm hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
>
{item.hashtag}
</button>
))}
</div>
<div className="mt-2 flex flex-col items-center justify-center gap-2">
<button <button
type="submit" type="submit"
disabled={!isDirty || !isValid} disabled={!isDirty || !isValid}
className="inline-flex h-11 w-full items-center justify-between gap-2 rounded-lg bg-blue-500 px-6 font-medium text-white hover:bg-blue-600 focus:outline-none disabled:opacity-50" className="inline-flex h-9 w-full items-center justify-between gap-2 rounded-lg bg-blue-500 px-6 font-medium text-white hover:bg-blue-600 focus:outline-none disabled:opacity-50"
> >
<span className="w-5" /> <span className="w-5" />
<span>Create</span> <span>Add</span>
<ArrowRightCircleIcon className="h-5 w-5" /> <ArrowRightCircleIcon className="h-5 w-5" />
</button> </button>
<button
type="button"
onClick={cancel}
className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-lg px-6 font-medium leading-none text-neutral-900 hover:bg-neutral-200 focus:outline-none disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-800"
>
Cancel
</button>
</div> </div>
</form> </form>
</div> </div>
</div> </WidgetWrapper>
); );
} }

View File

@ -10,6 +10,37 @@ export const FULL_RELAYS = [
export const FETCH_LIMIT = 20; export const FETCH_LIMIT = 20;
export const HASHTAGS = [
{ hashtag: '#food' },
{ hashtag: '#gaming' },
{ hashtag: '#nsfw' },
{ hashtag: '#bitcoin' },
{ hashtag: '#nostr' },
{ hashtag: '#nostrdesign' },
{ hashtag: '#security' },
{ hashtag: '#zap' },
{ hashtag: '#LFG' },
{ hashtag: '#zapchain' },
{ hashtag: '#shitcoin' },
{ hashtag: '#plebchain' },
{ hashtag: '#nodes' },
{ hashtag: '#hodl' },
{ hashtag: '#stacksats' },
{ hashtag: '#nokyc' },
{ hashtag: '#meme' },
{ hashtag: '#memes' },
{ hashtag: '#memestr' },
{ hashtag: '#nostriches' },
{ hashtag: '#dev' },
{ hashtag: '#anime' },
{ hashtag: '#waifu' },
{ hashtag: '#manga' },
{ hashtag: '#lume' },
{ hashtag: '#snort' },
{ hashtag: '#damus' },
{ hashtag: '#primal' },
];
export const WidgetKinds = { export const WidgetKinds = {
local: { local: {
network: 100, network: 100,

View File

@ -19,9 +19,24 @@ export function useWidget() {
const removeWidget = useMutation({ const removeWidget = useMutation({
mutationFn: async (id: string) => { mutationFn: async (id: string) => {
return await db.removeWidget(id); // Cancel any outgoing refetches
await queryClient.cancelQueries({ queryKey: ['widgets'] });
// Snapshot the previous value
const prevWidgets = queryClient.getQueryData(['widgets']);
// Optimistically update to the new value
queryClient.setQueryData(['widgets'], (prev: Widget[]) =>
prev.filter((t) => t.id !== id)
);
// Update in database
await db.removeWidget(id);
// Return a context object with the snapshotted value
return { prevWidgets };
}, },
onSuccess: () => { onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['widgets'] }); queryClient.invalidateQueries({ queryKey: ['widgets'] });
}, },
}); });