diff --git a/src/entities/_shared/account/components/profile-images.tsx b/src/entities/_shared/account/components/profile-images.tsx index 94276506..7f3f6596 100644 --- a/src/entities/_shared/account/components/profile-images.tsx +++ b/src/entities/_shared/account/components/profile-images.tsx @@ -1,13 +1,18 @@ import type { LazyLoadImageProps } from "react-lazy-load-image-component"; import { ByAccountId, type LiveUpdateParams } from "@/common/types"; -import { Avatar, AvatarImage, Skeleton } from "@/common/ui/layout/components"; +import { Avatar, AvatarFallback, AvatarImage, Skeleton } from "@/common/ui/layout/components"; import { LazyImage } from "@/common/ui/layout/components/LazyImage"; import { cn } from "@/common/ui/layout/utils"; -import { ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC } from "../constants"; +import { + ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC, + ACCOUNT_PROFILE_IMAGE_PLACEHOLDER_SRC, +} from "../constants"; import { useAccountSocialProfile } from "../hooks/social-profile"; +const getInitial = (accountId: string) => accountId?.trim()?.[0]?.toUpperCase() ?? "?"; + export type AccountProfilePictureProps = ByAccountId & LiveUpdateParams & { className?: string; @@ -26,10 +31,13 @@ export const AccountProfilePicture: React.FC = ({ + + {getInitial(accountId)} + ); }; diff --git a/src/entities/campaign/components/CampaignProgressBar.tsx b/src/entities/campaign/components/CampaignProgressBar.tsx index 42475024..12168b40 100644 --- a/src/entities/campaign/components/CampaignProgressBar.tsx +++ b/src/entities/campaign/components/CampaignProgressBar.tsx @@ -210,25 +210,13 @@ export const CampaignProgressBar: React.FC = ({ minAmount={`${minAmountFloat} ${token?.metadata.symbol ?? ""}`} minValuePercentage={((): number | undefined => { if (!minAmountFloat || !targetAmountFloat) return undefined; + if (isTargetMet) return undefined; - // Compute geometric min arrow position const rawMinPercent = (minAmountFloat / targetAmountFloat) * 100; const LEFT_PAD_PCT = 3; const RIGHT_PAD_PCT = 3; - const clampedMinPercent = Math.max( - LEFT_PAD_PCT, - Math.min(100 - RIGHT_PAD_PCT, rawMinPercent), - ); - - const PASSED_DELTA = 0.5; - - const minArrowPercent = - progressExact >= clampedMinPercent - ? Math.min(clampedMinPercent, Math.max(LEFT_PAD_PCT, progressExact - PASSED_DELTA)) - : clampedMinPercent; - - return minArrowPercent; + return Math.max(LEFT_PAD_PCT, Math.min(100 - RIGHT_PAD_PCT, rawMinPercent)); })()} value={progressPercentage} bgColor={color} diff --git a/src/entities/campaign/components/editor.tsx b/src/entities/campaign/components/editor.tsx index 370c31b1..8d9526f1 100644 --- a/src/entities/campaign/components/editor.tsx +++ b/src/entities/campaign/components/editor.tsx @@ -13,7 +13,7 @@ import { pinataHooks } from "@/common/services/pinata"; import { CampaignId } from "@/common/types"; import { TextAreaField, TextField } from "@/common/ui/form/components"; import { RichTextEditor } from "@/common/ui/form/components/richtext"; -import { Button, Form, FormField, Switch } from "@/common/ui/layout/components"; +import { Button, Form, FormField, Spinner, Switch } from "@/common/ui/layout/components"; import { cn } from "@/common/ui/layout/utils"; import { useWalletUserSession } from "@/common/wallet"; import { @@ -918,8 +918,19 @@ export const CampaignEditor = ({ existingData, campaignId, close }: CampaignEdit )} )} - diff --git a/src/entities/campaign/hooks/forms.ts b/src/entities/campaign/hooks/forms.ts index de23b984..c4711933 100644 --- a/src/entities/campaign/hooks/forms.ts +++ b/src/entities/campaign/hooks/forms.ts @@ -170,20 +170,18 @@ export const useCampaignForm = ({ campaignId, ftId, onUpdateSuccess }: CampaignF return new Date(time).getTime(); }; - const formatFullDateTime = (timestampMs: number) => { - const date = new Date(timestampMs); - const day = date.getDate(); + const formatRelativeFromNow = (timestampMs: number) => { + const diffMs = timestampMs - Date.now(); + if (diffMs <= 0) return "now"; - let suffix = "th"; - if (day % 10 === 1 && day !== 11) suffix = "st"; - else if (day % 10 === 2 && day !== 12) suffix = "nd"; - else if (day % 10 === 3 && day !== 13) suffix = "rd"; + const minutes = Math.round(diffMs / 60_000); + if (minutes < 60) return `${minutes} minute${minutes === 1 ? "" : "s"}`; - const month = date.toLocaleString("en-US", { month: "long" }); - const year = date.getFullYear(); - const time = date.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" }); + const hours = Math.round(minutes / 60); + if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"}`; - return `${day}${suffix} ${month} ${year}, ${time}`; + const days = Math.round(hours / 24); + return `${days} day${days === 1 ? "" : "s"}`; }; const handleDeleteCampaign = async () => { @@ -367,7 +365,7 @@ export const useCampaignForm = ({ campaignId, ftId, onUpdateSuccess }: CampaignF }; if (campaignId) { - campaignsContractClient + return campaignsContractClient .update_campaign({ args: { ...args, campaign_id: campaignId }, }) @@ -378,12 +376,12 @@ export const useCampaignForm = ({ campaignId, ftId, onUpdateSuccess }: CampaignF self.reset(values, { keepErrors: false }); toast({ - title: `You’ve successfully updated this campaign`, + title: "Campaign updated", description: (() => { const startMs = values.start_ms ? timeToMilliseconds(values.start_ms) : undefined; if (startMs && startMs > Date.now()) { - return `Campaign starts on ${formatFullDateTime(startMs)}.`; + return `Campaign starts in ${formatRelativeFromNow(startMs)}.`; } return "Campaign is live."; @@ -406,7 +404,7 @@ export const useCampaignForm = ({ campaignId, ftId, onUpdateSuccess }: CampaignF }); }); } else { - campaignsContractClient + return campaignsContractClient .create_campaign({ args }) .then(async (newCampaign) => { const startMs = values.start_ms ? timeToMilliseconds(values.start_ms) : undefined; @@ -422,10 +420,10 @@ export const useCampaignForm = ({ campaignId, ftId, onUpdateSuccess }: CampaignF } toast({ - title: `You’ve successfully created a campaign for ${values.name}.`, + title: "Campaign created", description: (() => { if (startMs && startMs > Date.now()) { - return `Campaign starts on ${formatFullDateTime(startMs)}.`; + return `Campaign starts in ${formatRelativeFromNow(startMs)}.`; } return "Campaign is live."; diff --git a/src/layout/components/floating-docs-button.tsx b/src/layout/components/floating-docs-button.tsx new file mode 100644 index 00000000..628c68cd --- /dev/null +++ b/src/layout/components/floating-docs-button.tsx @@ -0,0 +1,16 @@ +import { HelpCircle } from "lucide-react"; + +const DOCS_URL = "https://docs.potlock.io"; + +export const FloatingDocsButton = () => ( + + + +); diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index e5571d51..cfc8fe14 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -24,6 +24,7 @@ import { cn } from "@/common/ui/layout/utils"; import { WalletUserSessionProvider } from "@/common/wallet"; import { AppBar } from "@/layout/components/app-bar"; import { CampaignRouteLoading } from "@/layout/components/campaign-route-loading"; +import { FloatingDocsButton } from "@/layout/components/floating-docs-button"; import { store } from "@/store"; const lora = Lora({ @@ -98,6 +99,7 @@ export default function RootLayout({ Component, pageProps }: AppPropsWithLayout) > {getLayout()} + diff --git a/src/pages/campaigns.tsx b/src/pages/campaigns.tsx index 9f0b6b82..45838b2d 100644 --- a/src/pages/campaigns.tsx +++ b/src/pages/campaigns.tsx @@ -15,6 +15,9 @@ import { import { cn } from "@/common/ui/layout/utils"; import { useWalletUserSession } from "@/common/wallet"; import { CampaignCarouselItem, CampaignsList } from "@/entities/campaign"; +import { rootPathnames } from "@/navigation"; + +const FEATURED_CAMPAIGN_ON_CHAIN_IDS = [131, 106, 101, 91]; export const FeaturedCampaigns = ({ data, @@ -26,6 +29,10 @@ export const FeaturedCampaigns = ({ const [api, setApi] = useState(); const [current, setCurrent] = useState(0); + const featuredCampaigns = (data ?? []).filter((c) => + FEATURED_CAMPAIGN_ON_CHAIN_IDS.includes(c?.on_chain_id), + ); + useEffect(() => { if (!api) return; @@ -42,7 +49,7 @@ export const FeaturedCampaigns = ({ return () => clearInterval(interval); }, [api]); - if (!data?.length) { + if (!featuredCampaigns.length) { return <>; } @@ -53,7 +60,9 @@ export const FeaturedCampaigns = ({

Featured Campaigns

-

{current + 1}/4

+

+ {current + 1}/{featuredCampaigns.length} +

@@ -72,7 +81,7 @@ export const FeaturedCampaigns = ({
{showViewAll && ( @@ -81,10 +90,9 @@ export const FeaturedCampaigns = ({
- {data?.length && - data - ?.filter((data) => [131, 106, 101, 91].includes(data?.on_chain_id)) - ?.map((data) => )} + {featuredCampaigns.map((c) => ( + + ))} diff --git a/src/pages/feed/[account]/[block]/index.tsx b/src/pages/feed/[account]/[block]/index.tsx index 9532aaaf..6ddf3803 100644 --- a/src/pages/feed/[account]/[block]/index.tsx +++ b/src/pages/feed/[account]/[block]/index.tsx @@ -6,6 +6,7 @@ import remarkGfm from "remark-gfm"; import { fetchSinglePost, fetchTimeByBlockHeight } from "@/common/api/near-social-indexer"; import { IPFS_NEAR_SOCIAL_URL } from "@/common/constants"; +import { Skeleton } from "@/common/ui/layout/components"; import { LazyImage } from "@/common/ui/layout/components/LazyImage"; import { AccountProfilePicture } from "@/entities/_shared/account"; @@ -43,7 +44,26 @@ export default function FeedAccountBlockPostPage() { }, [account, block]); if (isLoading) { - return
Loading...
; + return ( +
+
+ + + +
+ +
+ + + + +
+ +
+ ); } if (!post) {