feat: added option to create album from potential albums
Signed-off-by: Varun Raj <mailme@varunraj.in>
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
Vendored
+9
@@ -26,3 +26,12 @@ export interface IAlbumOwner {
|
||||
profileImagePath: string;
|
||||
avatarColor: string;
|
||||
}
|
||||
|
||||
export interface IAlbumCreate {
|
||||
albumName: string;
|
||||
assetIds?: string[];
|
||||
albumUsers?: {
|
||||
role: string;
|
||||
userId: string;
|
||||
}[];
|
||||
}
|
||||
Reference in New Issue
Block a user