feat: added option to create album from potential albums

Signed-off-by: Varun Raj <mailme@varunraj.in>
This commit is contained in:
Varun Raj
2024-08-27 23:23:49 +05:30
parent 4d558d5d7a
commit dcad608869
7 changed files with 169 additions and 16 deletions
@@ -0,0 +1,86 @@
import React, { useEffect, useMemo, useState } from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../ui/dialog";
import { Button } from "../ui/button";
import { listAlbums } from "@/handlers/api/album.handler";
import { IAlbum, IAlbumCreate } from "@/types/album";
import { Input } from "../ui/input";
import { Label } from "../ui/label";
import { useToast } from "../ui/use-toast";
interface IProps {
onCreated?: (album: IAlbum) => Promise<void>;
onSubmit?: (data: IAlbumCreate) => Promise<any>;
assetIds?: string[];
}
export default function AlbumCreateDialog({ onSubmit, assetIds }: IProps) {
const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState({
albumName: "",
});
const { toast } = useToast();
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!onSubmit) return;
setLoading(true);
onSubmit(formData)
.then(() => {
toast({
title: "Album Created",
description: "Album created successfully",
});
setFormData({ albumName: "" });
setOpen(false);
})
.catch((e) => {
toast({
title: "Error",
description: "Failed to create album",
variant: "destructive",
});
})
.finally(() => {
setLoading(false);
});
};
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>Create Album</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create Album</DialogTitle>
<DialogDescription>
Create and add photos to a new album
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="flex flex-col gap-2">
<div className="flex flex-col gap-2">
<Label>Album Name</Label>
<Input
placeholder="Album Name"
onChange={(e) => {
setFormData({ ...formData, albumName: e.target.value });
}}
/>
</div>
<div className="self-end">
<Button disabled={loading}>
{loading ? "Creating Album" : "Create Album"}
</Button>
</div>
</form>
</DialogContent>
</Dialog>
);
}
+38 -13
View File
@@ -1,20 +1,45 @@
import Sidebar from "../shared/Sidebar"
import { Toaster } from "../ui/toaster"
import { IUser } from "@/types/user";
import Sidebar from "../shared/Sidebar";
import { Toaster } from "../ui/toaster";
import { ReactNode, useEffect, useState } from "react";
import { getMe } from "@/handlers/api/user.handler";
import UserContext from "@/contexts/CurrentUserContext";
import ErrorBlock from "../shared/ErrorBlock";
import Loader from "../ui/loader";
type RootLayoutProps = {
children: React.ReactNode
}
children: ReactNode;
};
export default function RootLayout({ children }: RootLayoutProps) {
const [user, setUser] = useState<IUser | null>(null);
const [loading, setLoading] = useState(true);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const fetchData = () => {
return getMe()
.then(setUser)
.catch((error) => {
setErrorMessage(error.message);
})
.finally(() => setLoading(false));
};
useEffect(() => {
fetchData();
}, []);
if (loading) return <Loader />
if (errorMessage) return <ErrorBlock description={errorMessage} />;
if (!user) return null;
return (
<div className="grid max-h-screen min-h-screen w-full md:grid-cols-[200px_1fr] lg:grid-cols-[240px_1fr]">
<Sidebar />
<div className="flex flex-col">
{children}
<UserContext.Provider value={user}>
<div className="grid max-h-screen min-h-screen w-full md:grid-cols-[200px_1fr] lg:grid-cols-[240px_1fr]">
<Sidebar />
<div className="flex flex-col">{children}</div>
<Toaster />
</div>
<Toaster />
</div>
)
</UserContext.Provider>
);
}
+1
View File
@@ -17,6 +17,7 @@ export const EXIF_DISTRIBUTION_PATH = (column: string) => BASE_API_ENDPOINT + "/
export const LIST_POTENTIAL_ALBUMS_DATES_PATH = BASE_API_ENDPOINT + "/albums/potential-albums-dates";
export const LIST_POTENTIAL_ALBUMS_ASSETS_PATH = BASE_API_ENDPOINT + "/albums/potential-albums-assets";
export const LIST_ALBUMS_PATH = BASE_PROXY_ENDPOINT + "/albums";
export const CREATE_ALBUM_PATH = BASE_PROXY_ENDPOINT + "/albums";
export const ADD_ASSETS_ALBUMS_PATH = (id: string) => BASE_PROXY_ENDPOINT + "/albums/" + id + "/assets";
// Assets
+13
View File
@@ -0,0 +1,13 @@
import { IUser } from "@/types/user";
import { createContext, useContext } from "react";
interface IUserContextType extends IUser {}
const UserContext = createContext<IUserContextType | null>(null);
export default UserContext;
export const useCurrentUser = () => {
const context = useContext(UserContext) as IUserContextType;
return context;
}
+6 -1
View File
@@ -1,6 +1,7 @@
import { ADD_ASSETS_ALBUMS_PATH, LIST_ALBUMS_PATH, LIST_POTENTIAL_ALBUMS_ASSETS_PATH, LIST_POTENTIAL_ALBUMS_DATES_PATH } from "@/config/routes";
import { ADD_ASSETS_ALBUMS_PATH, CREATE_ALBUM_PATH, LIST_ALBUMS_PATH, LIST_POTENTIAL_ALBUMS_ASSETS_PATH, LIST_POTENTIAL_ALBUMS_DATES_PATH } from "@/config/routes";
import { cleanUpAsset } from "@/helpers/asset.helper";
import API from "@/lib/api";
import { IAlbumCreate } from "@/types/album";
import { IAsset } from "@/types/asset";
interface IPotentialAlbumsDatesFilters {
@@ -27,3 +28,7 @@ export const listAlbums = async () => {
export const addAssetToAlbum = async (albumId: string, assetIds: string[]) => {
return API.put(ADD_ASSETS_ALBUMS_PATH(albumId), { ids: assetIds });
}
export const createAlbum = async (albumData: IAlbumCreate) => {
return API.post(CREATE_ALBUM_PATH, albumData);
}
+16 -2
View File
@@ -1,3 +1,4 @@
import AlbumCreateDialog from "@/components/albums/AlbumCreateDialog";
import AlbumSelectorDialog from "@/components/albums/AlbumSelectorDialog";
import PotentialAlbumsAssets from "@/components/albums/potential-albums/PotentialAlbumsAssets";
import PotentialAlbumsDates from "@/components/albums/potential-albums/PotentialAlbumsDates";
@@ -6,15 +7,17 @@ import Header from "@/components/shared/Header";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { useToast } from "@/components/ui/use-toast";
import { useCurrentUser } from "@/contexts/CurrentUserContext";
import PotentialAlbumContext, {
IPotentialAlbumConfig,
} from "@/contexts/PotentialAlbumContext";
import { addAssetToAlbum } from "@/handlers/api/album.handler";
import { IAlbum } from "@/types/album";
import { addAssetToAlbum, createAlbum } from "@/handlers/api/album.handler";
import { IAlbum, IAlbumCreate } from "@/types/album";
import React from "react";
export default function PotentialAlbums() {
const { toast } = useToast();
const { id } = useCurrentUser();
const [config, setConfig] = React.useState<IPotentialAlbumConfig>({
startDate: undefined,
@@ -49,6 +52,16 @@ export default function PotentialAlbums() {
});
};
const handleCreate = (formData: IAlbumCreate) => {
return createAlbum({
...formData,
assetIds: config.selectedIds,
albumUsers: [{
role: "editor",
userId: id,
}]
})
}
return (
<PageLayout className="!p-0 !mb-0">
<Header
@@ -84,6 +97,7 @@ export default function PotentialAlbums() {
</Button>
)}
<AlbumSelectorDialog onSelected={handleSelect} />
<AlbumCreateDialog onSubmit={handleCreate} assetIds={config.assets.map((a) => a.id)} />
</>
}
/>
+9
View File
@@ -26,3 +26,12 @@ export interface IAlbumOwner {
profileImagePath: string;
avatarColor: string;
}
export interface IAlbumCreate {
albumName: string;
assetIds?: string[];
albumUsers?: {
role: string;
userId: string;
}[];
}