Skip to content
Merged
20 changes: 11 additions & 9 deletions apps/web/actions/organization/upload-space-icon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
import { db } from "@cap/database";
import { getCurrentUser } from "@cap/database/auth/session";
import { spaces } from "@cap/database/schema";
import { serverEnv } from "@cap/env";
import { S3Buckets } from "@cap/web-backend";
import type { Space } from "@cap/web-domain";
import { ImageUpload, type Space } from "@cap/web-domain";
import { eq } from "drizzle-orm";
import { Effect, Option } from "effect";
import { revalidatePath } from "next/cache";
Expand Down Expand Up @@ -54,9 +53,11 @@ export async function uploadSpaceIcon(

// Prepare new file key
const fileExtension = file.name.split(".").pop();
const fileKey = `organizations/${
space.organizationId
}/spaces/${spaceId}/icon-${Date.now()}.${fileExtension}`;
const fileKey = ImageUpload.ImageKey.make(
`organizations/${
space.organizationId
}/spaces/${spaceId}/icon-${Date.now()}.${fileExtension}`,
);

const [bucket] = await S3Buckets.getBucketAccess(Option.none()).pipe(
runPromise,
Expand Down Expand Up @@ -89,12 +90,13 @@ export async function uploadSpaceIcon(
)
.pipe(runPromise);

const iconUrl = fileKey;

await db().update(spaces).set({ iconUrl }).where(eq(spaces.id, spaceId));
await db()
.update(spaces)
.set({ iconUrl: fileKey })
.where(eq(spaces.id, spaceId));

revalidatePath("/dashboard");
return { success: true, iconUrl };
return { success: true, iconUrl: fileKey };
} catch (error) {
console.error("Error uploading space icon:", error);
throw new Error(error instanceof Error ? error.message : "Upload failed");
Expand Down
11 changes: 6 additions & 5 deletions apps/web/app/(org)/dashboard/Contexts.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"use client";

import type { users } from "@cap/database/schema";
import { buildEnv } from "@cap/env";
import Cookies from "js-cookie";
import { usePathname } from "next/navigation";
import { redirect, usePathname } from "next/navigation";
import { createContext, useContext, useEffect, useState } from "react";
import { type CurrentUser, useCurrentUser } from "@/app/Layout/AuthContext";
import { UpgradeModal } from "@/components/UpgradeModal";
import type {
Organization,
Expand All @@ -21,7 +21,7 @@ type SharedContext = {
userSpaces: Spaces[] | null;
sharedSpaces: Spaces[] | null;
activeSpace: Spaces | null;
user: typeof users.$inferSelect;
user: CurrentUser;
userCapsCount: number | null;
isSubscribed: boolean;
toggleSidebarCollapsed: () => void;
Expand Down Expand Up @@ -56,7 +56,6 @@ export function DashboardContexts({
activeOrganization,
spacesData,
userCapsCount,
user,
isSubscribed,
organizationSettings,
userPreferences,
Expand All @@ -70,7 +69,6 @@ export function DashboardContexts({
activeOrganization: SharedContext["activeOrganization"];
spacesData: SharedContext["spacesData"];
userCapsCount: SharedContext["userCapsCount"];
user: SharedContext["user"];
isSubscribed: SharedContext["isSubscribed"];
organizationSettings: SharedContext["organizationSettings"];
userPreferences: SharedContext["userPreferences"];
Expand All @@ -79,6 +77,9 @@ export function DashboardContexts({
initialSidebarCollapsed: boolean;
referClicked: boolean;
}) {
const user = useCurrentUser();
if (!user) redirect("/login");

const [theme, setTheme] = useState<ITheme>(initialTheme);
const [sidebarCollapsed, setSidebarCollapsed] = useState(
initialSidebarCollapsed,
Expand Down
2 changes: 0 additions & 2 deletions apps/web/app/(org)/dashboard/_components/MobileTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ const Orgs = ({
<SignedImageUrl
image={activeOrg?.organization.iconUrl}
name={activeOrg?.organization.name ?? "No organization found"}
type="organization"
letterClass="text-xs"
className="relative flex-shrink-0 mx-auto size-6"
/>
Expand Down Expand Up @@ -138,7 +137,6 @@ const OrgsMenu = ({
<SignedImageUrl
image={organization.organization.iconUrl}
name={organization.organization.name}
type="organization"
letterClass="text-xs"
className="relative flex-shrink-0 size-5"
/>
Expand Down
2 changes: 0 additions & 2 deletions apps/web/app/(org)/dashboard/_components/Navbar/Items.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ const AdminNavItems = ({ toggleMobileNav }: Props) => {
name={
activeOrg?.organization.name ?? "No organization found"
}
type="organization"
letterClass={clsx(
sidebarCollapsed ? "text-sm" : "text-[13px]",
)}
Expand Down Expand Up @@ -216,7 +215,6 @@ const AdminNavItems = ({ toggleMobileNav }: Props) => {
<SignedImageUrl
image={organization.organization.iconUrl}
name={organization.organization.name}
type="organization"
letterClass="text-xs"
className="relative flex-shrink-0 size-5"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
Input,
Label,
} from "@cap/ui";
import type { ImageUpload } from "@cap/web-domain";
import { faLayerGroup } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
Expand All @@ -36,7 +37,7 @@ interface SpaceDialogProps {
id: string;
name: string;
members: string[];
iconUrl?: string;
iconUrl?: ImageUpload.ImageUrl;
} | null;
onSpaceUpdated?: () => void;
}
Expand Down Expand Up @@ -117,7 +118,7 @@ export interface NewSpaceFormProps {
id: string;
name: string;
members: string[];
iconUrl?: string;
iconUrl?: ImageUpload.ImageUrl;
} | null;
}

Expand Down Expand Up @@ -277,7 +278,6 @@ export const NewSpaceForm: React.FC<NewSpaceFormProps> = (props) => {
<FileInput
id="space-icon"
name="icon"
type="organization"
initialPreviewUrl={space?.iconUrl || null}
notDraggingClassName="hover:bg-gray-3"
onChange={setSelectedFile}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,6 @@ const SpacesList = ({ toggleMobileNav }: { toggleMobileNav?: () => void }) => {
<SignedImageUrl
image={space.iconUrl}
name={space.name}
type="organization"
letterClass={clsx(
sidebarCollapsed ? "text-sm" : "text-[11px]",
)}
Expand Down
4 changes: 1 addition & 3 deletions apps/web/app/(org)/dashboard/_components/Navbar/Top.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ const Top = () => {
<SignedImageUrl
image={activeSpace.iconUrl}
name={activeSpace?.name}
type="organization"
letterClass="text-xs"
className="relative flex-shrink-0 size-5"
/>
Expand Down Expand Up @@ -262,9 +261,8 @@ const User = () => {
>
<div className="flex items-center">
<SignedImageUrl
image={user.image}
image={user.imageUrl}
name={user.name ?? "User"}
type="user"
letterClass="text-xs lg:text-md"
className="flex-shrink-0 size-[24px] text-gray-12"
/>
Expand Down
19 changes: 10 additions & 9 deletions apps/web/app/(org)/dashboard/caps/Caps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@

import type { VideoMetadata } from "@cap/database/types";
import { Button } from "@cap/ui";
import type { Video } from "@cap/web-domain";
import type { ImageUpload, Video } from "@cap/web-domain";
import { faFolderPlus, faInfoCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Effect, Exit } from "effect";
import { useRouter, useSearchParams } from "next/navigation";
import { useEffect, useMemo, useRef, useState } from "react";
import { toast } from "sonner";
import { useEffectMutation } from "@/lib/EffectRuntime";
import { useEffectMutation, useRpcClient } from "@/lib/EffectRuntime";
import { useVideosAnalyticsQuery } from "@/lib/Queries/Analytics";
import { AnalyticsRequest } from "@/lib/Requests/AnalyticsRequest";
import { Rpc, withRpc } from "@/lib/Rpcs";
import { useDashboardContext } from "../Contexts";
import {
NewFolderDialog,
Expand All @@ -36,11 +34,14 @@ export type VideoData = {
totalComments: number;
totalReactions: number;
foldersData: FolderDataType[];
sharedOrganizations: { id: string; name: string; iconUrl?: string }[];
sharedOrganizations: {
id: string;
name: string;
iconUrl?: ImageUpload.ImageUrl | null;
}[];
sharedSpaces?: {
id: string;
name: string;
iconUrl: string;
isOrg: boolean;
organizationId: string;
}[];
Expand Down Expand Up @@ -147,12 +148,12 @@ export const Caps = ({
});
};

const rpc = useRpcClient();

const { mutate: deleteCaps, isPending: isDeletingCaps } = useEffectMutation({
mutationFn: Effect.fn(function* (ids: Video.VideoId[]) {
if (ids.length === 0) return;

const rpc = yield* Rpc;

const fiber = yield* Effect.gen(function* () {
const results = yield* Effect.all(
ids.map((id) => rpc.VideoDelete(id).pipe(Effect.exit)),
Expand Down Expand Up @@ -203,7 +204,7 @@ export const Caps = ({
});

const { mutate: deleteCap, isPending: isDeletingCap } = useEffectMutation({
mutationFn: (id: Video.VideoId) => withRpc((r) => r.VideoDelete(id)),
mutationFn: (id: Video.VideoId) => rpc.VideoDelete(id),
onSuccess: () => {
toast.success("Cap deleted successfully");
router.refresh();
Expand Down
13 changes: 7 additions & 6 deletions apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "@cap/ui";
import type { Video } from "@cap/web-domain";
import type { ImageUpload, Video } from "@cap/web-domain";
import { HttpClient } from "@effect/platform";
import {
faCheck,
Expand Down Expand Up @@ -41,7 +41,7 @@ import {
type ImageLoadingStatus,
VideoThumbnail,
} from "@/components/VideoThumbnail";
import { useEffectMutation } from "@/lib/EffectRuntime";
import { useEffectMutation, useRpcClient } from "@/lib/EffectRuntime";
import { withRpc } from "@/lib/Rpcs";
import { usePublicEnv } from "@/utils/public-env";
import { PasswordDialog } from "../PasswordDialog";
Expand All @@ -63,12 +63,12 @@ export interface CapCardProps extends PropsWithChildren {
sharedOrganizations?: {
id: string;
name: string;
iconUrl?: string | null;
iconUrl?: ImageUpload.ImageUrl | null;
}[];
sharedSpaces?: {
id: string;
name: string;
iconUrl?: string | null;
iconUrl?: ImageUpload.ImageUrl | null;
organizationId: string;
}[];
ownerName: string | null;
Expand Down Expand Up @@ -133,11 +133,12 @@ export const CapCard = ({
const [confirmOpen, setConfirmOpen] = useState(false);

const router = useRouter();
const rpc = useRpcClient();

const downloadMutation = useEffectMutation({
mutationFn: () =>
Effect.gen(function* () {
const result = yield* withRpc((r) => r.VideoGetDownloadInfo(cap.id));
const result = yield* rpc.VideoGetDownloadInfo(cap.id);
const httpClient = yield* HttpClient.HttpClient;
if (Option.isSome(result)) {
const fetchResponse = yield* httpClient.get(result.value.downloadUrl);
Expand Down Expand Up @@ -175,7 +176,7 @@ export const CapCard = ({
});

const duplicateMutation = useEffectMutation({
mutationFn: () => withRpc((r) => r.VideoDuplicate(cap.id)),
mutationFn: () => rpc.VideoDuplicate(cap.id),
onSuccess: () => {
router.refresh();
},
Expand Down
10 changes: 5 additions & 5 deletions apps/web/app/(org)/dashboard/caps/components/Folder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import { useRouter } from "next/navigation";
import { useEffect, useRef, useState } from "react";
import { toast } from "sonner";
import { moveVideoToFolder } from "@/actions/folders/moveVideoToFolder";
import { useEffectMutation } from "@/lib/EffectRuntime";
import { withRpc } from "@/lib/Rpcs";
import { useEffectMutation, useRpcClient } from "@/lib/EffectRuntime";
import { ConfirmationDialog } from "../../_components/ConfirmationDialog";
import { useDashboardContext, useTheme } from "../../Contexts";
import { registerDropTarget } from "../../folder/[id]/components/ClientCapCard";
Expand Down Expand Up @@ -70,8 +69,10 @@ const FolderCard = ({
}),
});

const rpc = useRpcClient();

const deleteFolder = useEffectMutation({
mutationFn: (id: Folder.FolderId) => withRpc((r) => r.FolderDelete(id)),
mutationFn: (id: Folder.FolderId) => rpc.FolderDelete(id),
onSuccess: () => {
router.refresh();
toast.success("Folder deleted successfully");
Expand All @@ -83,8 +84,7 @@ const FolderCard = ({
});

const updateFolder = useEffectMutation({
mutationFn: (data: Folder.FolderUpdate) =>
withRpc((r) => r.FolderUpdate(data)),
mutationFn: (data: Folder.FolderUpdate) => rpc.FolderUpdate(data),
onSuccess: () => {
toast.success("Folder name updated successfully");
router.refresh();
Expand Down
19 changes: 9 additions & 10 deletions apps/web/app/(org)/dashboard/caps/components/NewFolderDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ import { Option } from "effect";
import { useRouter } from "next/navigation";
import React, { useEffect, useRef, useState } from "react";
import { toast } from "sonner";
import { useEffectMutation } from "@/lib/EffectRuntime";
import { withRpc } from "@/lib/Rpcs";
import { useEffectMutation, useRpcClient } from "@/lib/EffectRuntime";
import {
BlueFolder,
type FolderHandle,
Expand Down Expand Up @@ -101,16 +100,16 @@ export const NewFolderDialog: React.FC<Props> = ({
),
);

const rpc = useRpcClient();

const createFolder = useEffectMutation({
mutationFn: (data: { name: string; color: Folder.FolderColor }) =>
withRpc((r) =>
r.FolderCreate({
name: data.name,
color: data.color,
spaceId: Option.fromNullable(spaceId),
parentId: Option.none(),
}),
),
rpc.FolderCreate({
name: data.name,
color: data.color,
spaceId: Option.fromNullable(spaceId),
parentId: Option.none(),
}),
onSuccess: () => {
setFolderName("");
setSelectedColor(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,6 @@ export const SettingsDialog = ({
}
}, [buildSettings, isOpen, settingsData]);

const isUserPro = userIsPro(user);

const saveHandler = async () => {
if (!settings) return;
setSaveLoading(true);
Expand Down Expand Up @@ -193,7 +191,7 @@ export const SettingsDialog = ({
</div>
<Switch
disabled={
(option.pro && !isUserPro) ||
(option.pro && !user.isPro) ||
((key === "disableSummary" || key === "disableChapters") &&
getEffectiveValue(
"disableTranscript" as keyof OrganizationSettings,
Expand Down
Loading