Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions src/entities/_shared/account/components/profile-images.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -26,10 +31,13 @@ export const AccountProfilePicture: React.FC<AccountProfilePictureProps> = ({
<Avatar className={cn("h-3 w-3", className)}>
<AvatarImage
alt={`Profile picture of ${accountId}`}
src={avatar.url}
src={avatar.url || ACCOUNT_PROFILE_IMAGE_PLACEHOLDER_SRC}
width={40}
height={40}
/>
<AvatarFallback className="bg-[#f0e7df] text-sm font-semibold text-[#7B7B7B]">
{getInitial(accountId)}
</AvatarFallback>
</Avatar>
);
};
Expand Down
16 changes: 2 additions & 14 deletions src/entities/campaign/components/CampaignProgressBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -210,25 +210,13 @@ export const CampaignProgressBar: React.FC<CampaignProgressBarProps> = ({
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}
Expand Down
17 changes: 14 additions & 3 deletions src/entities/campaign/components/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -918,8 +918,19 @@ export const CampaignEditor = ({ existingData, campaignId, close }: CampaignEdit
)}
</div>
)}
<Button variant="brand-filled" disabled={isDisabled} type="submit">
{isUpdate ? "Update" : "Create"} Campaign
<Button
variant="brand-filled"
disabled={isDisabled || form.formState.isSubmitting}
type="submit"
>
{form.formState.isSubmitting ? (
<span className="flex items-center gap-2">
<Spinner className="h-4 w-4" />
{isUpdate ? "Updating..." : "Creating..."}
</span>
) : (
<>{isUpdate ? "Update" : "Create"} Campaign</>
)}
</Button>
</div>

Expand Down
32 changes: 15 additions & 17 deletions src/entities/campaign/hooks/forms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -367,7 +365,7 @@ export const useCampaignForm = ({ campaignId, ftId, onUpdateSuccess }: CampaignF
};

if (campaignId) {
campaignsContractClient
return campaignsContractClient
.update_campaign({
args: { ...args, campaign_id: campaignId },
})
Expand All @@ -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.";
Expand All @@ -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;
Expand All @@ -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.";
Expand Down
16 changes: 16 additions & 0 deletions src/layout/components/floating-docs-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { HelpCircle } from "lucide-react";

const DOCS_URL = "https://docs.potlock.io";

export const FloatingDocsButton = () => (
<a
href={DOCS_URL}
target="_blank"
rel="noopener noreferrer"
aria-label="Open documentation"
title="Docs"
className="fixed bottom-5 right-5 z-50 flex h-12 w-12 items-center justify-center rounded-full bg-[#dd3345] text-white shadow-lg transition-transform hover:scale-105 hover:bg-[#c52d3d] focus:outline-none focus:ring-2 focus:ring-[#dd3345] focus:ring-offset-2"
>
<HelpCircle className="h-6 w-6" />
</a>
);
2 changes: 2 additions & 0 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -98,6 +99,7 @@ export default function RootLayout({ Component, pageProps }: AppPropsWithLayout)
>
<AppBar />
<CampaignRouteLoading>{getLayout(<Component {...pageProps} />)}</CampaignRouteLoading>
<FloatingDocsButton />
</div>
</TooltipProvider>
</NiceModalProvider>
Expand Down
22 changes: 15 additions & 7 deletions src/pages/campaigns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -26,6 +29,10 @@ export const FeaturedCampaigns = ({
const [api, setApi] = useState<CarouselApi>();
const [current, setCurrent] = useState(0);

const featuredCampaigns = (data ?? []).filter((c) =>
FEATURED_CAMPAIGN_ON_CHAIN_IDS.includes(c?.on_chain_id),
);

useEffect(() => {
if (!api) return;

Expand All @@ -42,7 +49,7 @@ export const FeaturedCampaigns = ({
return () => clearInterval(interval);
}, [api]);

if (!data?.length) {
if (!featuredCampaigns.length) {
return <></>;
}

Expand All @@ -53,7 +60,9 @@ export const FeaturedCampaigns = ({
<h1 className="text-sm font-medium uppercase leading-6 tracking-[1.12px] text-[#292929]">
Featured Campaigns
</h1>
<p className="text-[18px]">{current + 1}/4</p>
<p className="text-[18px]">
{current + 1}/{featuredCampaigns.length}
</p>
</div>
<div className="flex items-center gap-4">
<div className="flex gap-4">
Expand All @@ -72,7 +81,7 @@ export const FeaturedCampaigns = ({
</div>
{showViewAll && (
<Button asChild variant="brand-tonal" className="h-8 shrink-0 bg-transparent text-xs">
<Link href="/campaigns" className="text-brand-primary">
<Link href={rootPathnames.CAMPAIGNS} className="text-brand-primary">
VIEW ALL
</Link>
</Button>
Expand All @@ -81,10 +90,9 @@ export const FeaturedCampaigns = ({
</div>
<Carousel opts={{ loop: true }} setApi={setApi}>
<CarouselContent>
{data?.length &&
data
?.filter((data) => [131, 106, 101, 91].includes(data?.on_chain_id))
?.map((data) => <CampaignCarouselItem key={data.on_chain_id} data={data} />)}
{featuredCampaigns.map((c) => (
<CampaignCarouselItem key={c.on_chain_id} data={c} />
))}
</CarouselContent>
</Carousel>
</div>
Expand Down
22 changes: 21 additions & 1 deletion src/pages/feed/[account]/[block]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -43,7 +44,26 @@ export default function FeedAccountBlockPostPage() {
}, [account, block]);

if (isLoading) {
return <div>Loading...</div>;
return (
<div
style={{ boxShadow: "0px 8px 24px rgba(149, 157, 165, 0.2)" }}
className="2xl-container w-full rounded-2xl p-8 px-5 pb-12 md:px-10"
>
<div className="mb-4 flex items-center space-x-2">
<Skeleton className="h-8 w-8 rounded-full" />
<Skeleton className="h-5 w-40" />
<Skeleton className="h-4 w-24" />
</div>
<Skeleton className="mb-3 h-4 w-1/3" />
<div className="space-y-2">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-11/12" />
<Skeleton className="h-4 w-10/12" />
<Skeleton className="h-4 w-8/12" />
</div>
<Skeleton className="mt-6 h-64 w-full" />
</div>
);
}

if (!post) {
Expand Down
Loading