mirror of
https://github.com/fergalmoran/opengifame.git
synced 2025-12-25 11:09:40 +00:00
Clean up upload form
This commit is contained in:
8519
package-lock.json
generated
8519
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
@@ -16,7 +16,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/drizzle-adapter": "^1.4.2",
|
"@auth/drizzle-adapter": "^1.4.2",
|
||||||
"@headlessui/react": "^2.1.5",
|
"@headlessui/react": "^2.1.6",
|
||||||
"@hookform/resolvers": "^3.9.0",
|
"@hookform/resolvers": "^3.9.0",
|
||||||
"@radix-ui/react-accordion": "^1.2.0",
|
"@radix-ui/react-accordion": "^1.2.0",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.1",
|
"@radix-ui/react-alert-dialog": "^1.1.1",
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||||
"@radix-ui/react-tooltip": "^1.1.2",
|
"@radix-ui/react-tooltip": "^1.1.2",
|
||||||
"@t3-oss/env-nextjs": "^0.11.1",
|
"@t3-oss/env-nextjs": "^0.11.1",
|
||||||
"@tanstack/react-query": "^5.55.1",
|
"@tanstack/react-query": "^5.55.4",
|
||||||
"@trpc/client": "^11.0.0-rc.446",
|
"@trpc/client": "^11.0.0-rc.446",
|
||||||
"@trpc/react-query": "^11.0.0-rc.446",
|
"@trpc/react-query": "^11.0.0-rc.446",
|
||||||
"@trpc/server": "^11.0.0-rc.446",
|
"@trpc/server": "^11.0.0-rc.446",
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
"loglevel": "^1.9.2",
|
"loglevel": "^1.9.2",
|
||||||
"loglevel-plugin-prefix": "^0.8.4",
|
"loglevel-plugin-prefix": "^0.8.4",
|
||||||
"lucide-react": "^0.439.0",
|
"lucide-react": "^0.439.0",
|
||||||
"next": "^14.2.8",
|
"next": "^14.2.9",
|
||||||
"next-auth": "^4.24.7",
|
"next-auth": "^4.24.7",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"postgres": "^3.4.4",
|
"postgres": "^3.4.4",
|
||||||
@@ -91,21 +91,21 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@faker-js/faker": "^9.0.0",
|
"@faker-js/faker": "^9.0.0",
|
||||||
"@types/eslint": "^8.56.12",
|
"@types/eslint": "^9.6.1",
|
||||||
"@types/node": "^22.5.4",
|
"@types/node": "^22.5.4",
|
||||||
"@types/react": "^18.3.5",
|
"@types/react": "^18.3.5",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.4.0",
|
"@typescript-eslint/eslint-plugin": "^8.5.0",
|
||||||
"@typescript-eslint/parser": "^8.4.0",
|
"@typescript-eslint/parser": "^8.5.0",
|
||||||
"drizzle-kit": "^0.24.2",
|
"drizzle-kit": "^0.24.2",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^9.10.0",
|
||||||
"eslint-config-next": "^14.2.8",
|
"eslint-config-next": "^14.2.9",
|
||||||
"eslint-plugin-drizzle": "^0.2.3",
|
"eslint-plugin-drizzle": "^0.2.3",
|
||||||
"postcss": "^8.4.45",
|
"postcss": "^8.4.45",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.6",
|
"prettier-plugin-tailwindcss": "^0.6.6",
|
||||||
"tailwindcss": "^3.4.10",
|
"tailwindcss": "^3.4.10",
|
||||||
"typescript": "^5.5.4"
|
"typescript": "^5.6.2"
|
||||||
},
|
},
|
||||||
"ct3aMetadata": {
|
"ct3aMetadata": {
|
||||||
"initVersion": "7.37.0"
|
"initVersion": "7.37.0"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
@@ -8,23 +8,20 @@ interface AuthLayoutProps {
|
|||||||
|
|
||||||
export default function AuthLayout({ children }: AuthLayoutProps) {
|
export default function AuthLayout({ children }: AuthLayoutProps) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen">
|
<div className="container flex h-screen w-screen flex-col items-center ">
|
||||||
<div className="container flex h-screen w-screen flex-col items-center justify-center">
|
<Link
|
||||||
<Link
|
href="/"
|
||||||
href="/"
|
className={cn(
|
||||||
className={cn(
|
buttonVariants({ variant: "ghost" }),
|
||||||
buttonVariants({ variant: "ghost" }),
|
"absolute left-4 top-4 mt-8 md:left-8 md:top-8",
|
||||||
"absolute left-4 top-4 md:left-8 md:top-8",
|
)}
|
||||||
)}
|
>
|
||||||
>
|
<>
|
||||||
<>
|
<Icons.chevronLeft className="mr-2 h-4 w-4" />
|
||||||
<Icons.chevronLeft className="mr-2 h-4 w-4" />
|
Back
|
||||||
Back
|
</>
|
||||||
</>
|
</Link>
|
||||||
</Link>
|
<div className="mt-8">{children}</div>
|
||||||
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
5
src/app/(site)/upload/page.tsx
Normal file
5
src/app/(site)/upload/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import UploadPage from "@/components/pages/upload-page";
|
||||||
|
|
||||||
|
const Upload = () => <UploadPage />;
|
||||||
|
|
||||||
|
export default Upload;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Roboto as font } from "next/font/google";
|
import { Inknut_Antiqua as font } from "next/font/google";
|
||||||
import "@/styles/globals.css";
|
import "@/styles/globals.css";
|
||||||
|
|
||||||
import { type Metadata, type Viewport } from "next";
|
import { type Metadata, type Viewport } from "next";
|
||||||
@@ -13,6 +13,7 @@ import TopNavbar from "@/components/navbar/top-navbar";
|
|||||||
import { dashboardConfig } from "@/config/top-nav.config";
|
import { dashboardConfig } from "@/config/top-nav.config";
|
||||||
import { siteConfig } from "@/config/site.config";
|
import { siteConfig } from "@/config/site.config";
|
||||||
import { getServerSession } from "next-auth";
|
import { getServerSession } from "next-auth";
|
||||||
|
import { SessionProvider } from "next-auth/react";
|
||||||
|
|
||||||
export const viewport: Viewport = {
|
export const viewport: Viewport = {
|
||||||
themeColor: [
|
themeColor: [
|
||||||
@@ -21,8 +22,8 @@ export const viewport: Viewport = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
const f = font({
|
const f = font({
|
||||||
weight: "400",
|
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
|
weight: ["400"],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ const SignInForm: React.FC = () => {
|
|||||||
logger.debug("signin", "result", result);
|
logger.debug("signin", "result", result);
|
||||||
if (result?.status === 200) {
|
if (result?.status === 200) {
|
||||||
router.push("/");
|
router.push("/");
|
||||||
|
window.location.reload();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("SignInForm", "error", error);
|
logger.error("SignInForm", "error", error);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
type Icon as LucideIcon,
|
type Icon as LucideIcon,
|
||||||
Terminal,
|
Terminal,
|
||||||
LogIn,
|
LogIn,
|
||||||
|
Upload,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
export type Icon = typeof LucideIcon;
|
export type Icon = typeof LucideIcon;
|
||||||
@@ -112,4 +113,5 @@ export const Icons = {
|
|||||||
),
|
),
|
||||||
twitter: Twitter,
|
twitter: Twitter,
|
||||||
check: Check,
|
check: Check,
|
||||||
|
upload: Upload,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import React from "react";
|
|||||||
import { type NavItem } from "@/types";
|
import { type NavItem } from "@/types";
|
||||||
import { type Session } from "next-auth";
|
import { type Session } from "next-auth";
|
||||||
|
|
||||||
import { Icons } from "../icons";
|
import { Icons } from "@/components/icons";
|
||||||
import { siteConfig } from "@/config/site.config";
|
import { siteConfig } from "@/config/site.config";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useSelectedLayoutSegment } from "next/navigation";
|
import { useSelectedLayoutSegment } from "next/navigation";
|
||||||
|
|
||||||
import LoginButton from "../widgets/login/login-button";
|
import LoginButton from "@/components/widgets/login/login-button";
|
||||||
|
|
||||||
type TopNavbarProps = {
|
type TopNavbarProps = {
|
||||||
items: NavItem[];
|
items: NavItem[];
|
||||||
|
|||||||
137
src/components/pages/upload-page.tsx
Normal file
137
src/components/pages/upload-page.tsx
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
"use client";
|
||||||
|
import React from "react";
|
||||||
|
import { Form, FormProvider, useForm } from "react-hook-form";
|
||||||
|
import { SubmitHandler, Controller } from "react-hook-form";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import ImageUpload from "@/components/widgets/image-upload";
|
||||||
|
import TaggedInput from "@/components/widgets/tagged-input";
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
|
||||||
|
type FormValues = {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
tags: string[];
|
||||||
|
image: string | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const UploadPage: React.FC = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const form = useForm<FormValues>({
|
||||||
|
defaultValues: {
|
||||||
|
title: "",
|
||||||
|
description: "",
|
||||||
|
tags: [],
|
||||||
|
image: undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const _submit: SubmitHandler<FormValues> = async (data) => {
|
||||||
|
console.log(data);
|
||||||
|
if (data.image) {
|
||||||
|
const body = new FormData();
|
||||||
|
body.append("title", data.title);
|
||||||
|
body.append("description", data.description);
|
||||||
|
body.append("tags", data.tags.join("|"));
|
||||||
|
body.append("file", data.image);
|
||||||
|
const response = await fetch("api/upload", {
|
||||||
|
method: "POST",
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
if (response.status === 201) {
|
||||||
|
await router.replace("/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="md:grid md:grid-cols-3 md:gap-6">
|
||||||
|
<div className="md:col-span-1">
|
||||||
|
<div className="px-4 sm:px-0">
|
||||||
|
<h3 className="text-lg font-extrabold leading-6">Upload a new gif</h3>
|
||||||
|
<p className="text-base-content/70 my-3 text-sm">
|
||||||
|
The more info you can give us the better.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="md:col-span-2">
|
||||||
|
<FormProvider {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(_submit)}>
|
||||||
|
<div className="shadow sm:overflow-hidden sm:rounded-md">
|
||||||
|
<div className="space-y-4 px-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="title"
|
||||||
|
defaultValue={"fergal.moran+opengifame@gmail.com"}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Title</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input type="text" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
{form.formState.errors.title && (
|
||||||
|
<FormMessage>
|
||||||
|
{form.formState.errors.title.message}
|
||||||
|
</FormMessage>
|
||||||
|
)}
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="description"
|
||||||
|
defaultValue={"fergal.moran+opengifame@gmail.com"}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Description</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea {...field} rows={3} />
|
||||||
|
</FormControl>
|
||||||
|
{form.formState.errors.description && (
|
||||||
|
<FormMessage>
|
||||||
|
{form.formState.errors.description.message}
|
||||||
|
</FormMessage>
|
||||||
|
)}
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
control={form.control}
|
||||||
|
name="image"
|
||||||
|
render={({ field: { value, onChange } }) => (
|
||||||
|
<ImageUpload value={value} onChange={onChange} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="divider pt-4">optional stuff</div>
|
||||||
|
<Controller
|
||||||
|
control={form.control}
|
||||||
|
name="tags"
|
||||||
|
render={({ field: { value, onChange } }) => (
|
||||||
|
<TaggedInput
|
||||||
|
label="Tags"
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-full px-4 py-3 text-right">
|
||||||
|
<button type="submit" className="btn btn-primary w-full">
|
||||||
|
Upload Gif
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</FormProvider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UploadPage;
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { api } from "@/trpc/react";
|
import { api } from "@/trpc/react";
|
||||||
|
import ImageUpload from "./widgets/image-upload";
|
||||||
|
|
||||||
export function TrendingImages() {
|
export function TrendingImages() {
|
||||||
const [latestPost] = api.post.getLatest.useSuspenseQuery();
|
const [latestPost] = api.post.getLatest.useSuspenseQuery();
|
||||||
@@ -23,21 +24,13 @@ export function TrendingImages() {
|
|||||||
) : (
|
) : (
|
||||||
<p>No images yet.</p>
|
<p>No images yet.</p>
|
||||||
)}
|
)}
|
||||||
<form
|
|
||||||
onSubmit={(e) => {
|
<ImageUpload
|
||||||
e.preventDefault();
|
value="Farts"
|
||||||
createPost.mutate({ name });
|
onChange={(e) => {
|
||||||
|
console.log("trending-images", "uploading", e);
|
||||||
}}
|
}}
|
||||||
className="flex flex-col gap-2 my-4"
|
/>
|
||||||
>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="rounded-full bg-white/10 px-10 py-3 font-semibold transition hover:bg-white/20"
|
|
||||||
disabled={createPost.isPending}
|
|
||||||
>
|
|
||||||
{createPost.isPending ? "Submitting..." : "Upload an image"}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
24
src/components/ui/textarea.tsx
Normal file
24
src/components/ui/textarea.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
export interface TextareaProps
|
||||||
|
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||||
|
|
||||||
|
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||||
|
({ className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
className={cn(
|
||||||
|
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Textarea.displayName = "Textarea"
|
||||||
|
|
||||||
|
export { Textarea }
|
||||||
@@ -1,61 +1,59 @@
|
|||||||
// /* eslint-disable @next/next/no-img-element */
|
/* eslint-disable @next/next/no-img-element */
|
||||||
// import React from "react";
|
import React from "react";
|
||||||
// import { HiOutlineXMark } from "react-icons/hi2";
|
import { Icons } from "../icons";
|
||||||
// import { ImUpload2 } from "react-icons/im";
|
interface IImageUploadProps {
|
||||||
// interface IImageUploadProps {
|
value: string | undefined;
|
||||||
// value: string | undefined;
|
onChange: (image: File) => void;
|
||||||
// onChange: (image: File) => void;
|
}
|
||||||
// }
|
const ImageUpload: React.FC<IImageUploadProps> = ({ onChange }) => {
|
||||||
// const ImageUpload: React.FC<IImageUploadProps> = ({ onChange }) => {
|
const [image, setImage] = React.useState<string>();
|
||||||
// const [image, setImage] = React.useState<string>();
|
const onImageChange = ($event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
// const onImageChange = ($event: React.ChangeEvent<HTMLInputElement>) => {
|
console.log("ImageUpload", "onImageChange", $event);
|
||||||
// console.log("ImageUpload", "onImageChange", $event);
|
if ($event.target.files?.[0]) {
|
||||||
// if ($event.target.files) {
|
const url = URL.createObjectURL($event.target.files[0]);
|
||||||
// const url = URL.createObjectURL($event.target.files[0]);
|
setImage(url);
|
||||||
// setImage(url);
|
onChange($event.target.files[0]);
|
||||||
// onChange($event.target.files[0]);
|
}
|
||||||
// }
|
};
|
||||||
// };
|
return (
|
||||||
// return (
|
<div className="mt-1 flex justify-center rounded-md border-2 border-dashed border-secondary px-6 pb-6 pt-5">
|
||||||
// <div className="mt-1 flex justify-center rounded-md border-2 border-dashed border-secondary px-6 pb-6 pt-5">
|
{image ? (
|
||||||
// {image ? (
|
<div>
|
||||||
// <div>
|
<div className="relative">
|
||||||
// <div className="relative">
|
<img src={image} alt="Preview" />
|
||||||
// <img src={image} alt="Preview" />
|
<button
|
||||||
// <button
|
type="button"
|
||||||
// type="button"
|
className="absolute right-0 top-0 m-2 rounded-full p-2"
|
||||||
// className="absolute right-0 top-0 m-2 rounded-full p-2"
|
onClick={() => setImage("")}
|
||||||
// onClick={() => setImage("")}
|
>
|
||||||
// >
|
<Icons.close className="h-5 w-5" />
|
||||||
// {""}
|
</button>
|
||||||
// <HiOutlineXMark className="h-5 w-5" />
|
</div>
|
||||||
// </button>
|
</div>
|
||||||
// </div>
|
) : (
|
||||||
// </div>
|
<div className="space-y-1 text-center">
|
||||||
// ) : (
|
<Icons.upload className="text-base-content/70 mx-auto mb-3 h-8 w-8" />
|
||||||
// <div className="space-y-1 text-center">
|
<div className="text-base-content flex text-sm">
|
||||||
// <ImUpload2 className="text-base-content/70 mx-auto mb-3 h-8 w-8" />
|
<label
|
||||||
// <div className="text-base-content flex text-sm">
|
htmlFor="gif-upload"
|
||||||
// <label
|
className="badge badge-primary relative cursor-pointer rounded-md font-medium hover:text-accent"
|
||||||
// htmlFor="gif-upload"
|
>
|
||||||
// className="badge badge-primary relative cursor-pointer rounded-md font-medium hover:text-accent"
|
<span>Upload a file</span>
|
||||||
// >
|
<input
|
||||||
// <span>Upload a file</span>
|
accept="image/gif,video/mp4,video/mov,video/quicktime,video/webm,youtube,vimeo"
|
||||||
// <input
|
id="gif-upload"
|
||||||
// accept="image/gif,video/mp4,video/mov,video/quicktime,video/webm,youtube,vimeo"
|
type="file"
|
||||||
// id="gif-upload"
|
className="sr-only"
|
||||||
// type="file"
|
onChange={onImageChange}
|
||||||
// className="sr-only"
|
/>
|
||||||
// onChange={onImageChange}
|
</label>
|
||||||
// />
|
<p className="pl-1">or drag and drop</p>
|
||||||
// </label>
|
</div>
|
||||||
// <p className="pl-1">or drag and drop</p>
|
<p className="text-xs">PNG, JPG, GIF up to 10MB</p>
|
||||||
// </div>
|
</div>
|
||||||
// <p className="text-xs">PNG, JPG, GIF up to 10MB</p>
|
)}
|
||||||
// </div>
|
</div>
|
||||||
// )}
|
);
|
||||||
// </div>
|
};
|
||||||
// );
|
|
||||||
// };
|
|
||||||
|
|
||||||
// export default ImageUpload;
|
export default ImageUpload;
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { signIn } from "next-auth/react";
|
import { signIn } from "next-auth/react";
|
||||||
import { RiLoginCircleLine } from "react-icons/ri";
|
import UserNavDropdown from "@/components/widgets/user-nav-dropdown";
|
||||||
import UserNavDropdown from "../user-nav-dropdown";
|
|
||||||
import { type Session } from "next-auth";
|
import { type Session } from "next-auth";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Icons } from "@/components/icons";
|
import { Icons } from "@/components/icons";
|
||||||
|
|||||||
@@ -1,143 +1,143 @@
|
|||||||
// import { logger } from "@/lib/logger";
|
import { logger } from "@/lib/logger";
|
||||||
// import React, { KeyboardEventHandler } from "react";
|
import React, { KeyboardEventHandler } from "react";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
|
||||||
// interface ITaggedInputProps {
|
interface ITaggedInputProps {
|
||||||
// label: string;
|
label: string;
|
||||||
// value: string[];
|
value: string[];
|
||||||
// onChange: (tags: string[]) => void;
|
onChange: (tags: string[]) => void;
|
||||||
// }
|
}
|
||||||
// const TaggedInput: React.FC<ITaggedInputProps> = ({
|
const TaggedInput: React.FC<ITaggedInputProps> = ({
|
||||||
// label,
|
label,
|
||||||
// value,
|
value,
|
||||||
// onChange,
|
onChange,
|
||||||
// }) => {
|
}) => {
|
||||||
// const [isSearching, setIsSearching] = React.useState(false);
|
const [isSearching, setIsSearching] = React.useState(false);
|
||||||
// const [searchText, setSearchText] = React.useState<string>("");
|
const [searchText, setSearchText] = React.useState<string>("");
|
||||||
// const [searchResults, setSearchResults] = React.useState<Array<string>>([]);
|
const [searchResults, setSearchResults] = React.useState<Array<string>>([]);
|
||||||
// const [tags, setTags] = React.useState<Array<string>>(value);
|
const [tags, setTags] = React.useState<Array<string>>(value);
|
||||||
|
|
||||||
// React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
// logger.debug("TaggedInput", "callingOnChange", tags);
|
logger.debug("TaggedInput", "callingOnChange", tags);
|
||||||
// onChange(tags);
|
onChange(tags);
|
||||||
// }, [tags, onChange]);
|
}, [tags, onChange]);
|
||||||
// const removeTag = (tag: string) => {
|
const removeTag = (tag: string) => {
|
||||||
// setTags(tags.filter((obj) => obj !== tag));
|
setTags(tags.filter((obj) => obj !== tag));
|
||||||
// };
|
};
|
||||||
// const searchTags = async (query: string) => {
|
const searchTags = async (query: string) => {
|
||||||
// if (!query) {
|
if (!query) {
|
||||||
// setSearchResults([]);
|
setSearchResults([]);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
// setIsSearching(true);
|
setIsSearching(true);
|
||||||
// const response = await fetch(`api/tags/search?q=${searchText}`);
|
const response = await fetch(`api/tags/search?q=${searchText}`);
|
||||||
// if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
// const results = await response.json();
|
const results = await response.json();
|
||||||
// setSearchResults(results.map((r: { name: string }) => r.name));
|
setSearchResults(results.map((r: { name: string }) => r.name));
|
||||||
// }
|
}
|
||||||
// setIsSearching(false);
|
setIsSearching(false);
|
||||||
// };
|
};
|
||||||
// const handleChange = async ($event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChange = async ($event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
// const { value } = $event.target;
|
const { value } = $event.target;
|
||||||
// setSearchText(value);
|
setSearchText(value);
|
||||||
// await searchTags(value);
|
await searchTags(value);
|
||||||
// };
|
};
|
||||||
// const handleKeyPress = ($event: React.KeyboardEvent<HTMLInputElement>) => {
|
const handleKeyPress = ($event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
// if ($event.code === "Enter" || $event.code === "NumpadEnter") {
|
if ($event.code === "Enter" || $event.code === "NumpadEnter") {
|
||||||
// __addTag(searchText);
|
__addTag(searchText);
|
||||||
// }
|
}
|
||||||
// };
|
};
|
||||||
// const __addTag = (tag: string) => {
|
const __addTag = (tag: string) => {
|
||||||
// setTags([...tags, tag]);
|
setTags([...tags, tag]);
|
||||||
// setSearchResults([]);
|
setSearchResults([]);
|
||||||
// setIsSearching(false);
|
setIsSearching(false);
|
||||||
// setSearchText("");
|
setSearchText("");
|
||||||
// };
|
};
|
||||||
// const doResultClick = ($event: any) =>
|
const doResultClick = ($event: any) =>
|
||||||
// __addTag($event.target.textContent as string);
|
__addTag($event.target.textContent as string);
|
||||||
// return (
|
return (
|
||||||
// <>
|
<>
|
||||||
// <label htmlFor="{name}" className="block text-sm font-medium">
|
<label htmlFor="{name}" className="block text-sm font-medium">
|
||||||
// {label}
|
{label}
|
||||||
// </label>
|
</label>
|
||||||
// <div className="border-accent flex w-full rounded-lg border align-middle text-sm shadow-sm">
|
<div className="flex w-full rounded-lg align-middle text-sm shadow-sm">
|
||||||
// <div className="flex flex-row space-x-1 pl-2 pt-3">
|
<div className="flex flex-row space-x-1">
|
||||||
// {tags &&
|
{tags &&
|
||||||
// tags.map((tag) => (
|
tags.map((tag) => (
|
||||||
// <span key={tag} className="badge badge-primary badge-lg py-0.5">
|
<span key={tag} className="badge badge-primary badge-lg py-0.5">
|
||||||
// {tag}
|
{tag}
|
||||||
// <button
|
<button
|
||||||
// onClick={() => removeTag(tag)}
|
onClick={() => removeTag(tag)}
|
||||||
// type="button"
|
type="button"
|
||||||
// className="ml-0.5 inline-flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full"
|
className="ml-0.5 inline-flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full"
|
||||||
// >
|
>
|
||||||
// <span className="sr-only">{tag}</span>
|
<span className="sr-only">{tag}</span>
|
||||||
// <svg
|
<svg
|
||||||
// className="h-2 w-2"
|
className="h-2 w-2"
|
||||||
// stroke="currentColor"
|
stroke="currentColor"
|
||||||
// fill="none"
|
fill="none"
|
||||||
// viewBox="0 0 8 8"
|
viewBox="0 0 8 8"
|
||||||
// >
|
>
|
||||||
// <path
|
<path
|
||||||
// strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
// strokeWidth={1.5}
|
strokeWidth={1.5}
|
||||||
// d="M1 1l6 6m0-6L1 7"
|
d="M1 1l6 6m0-6L1 7"
|
||||||
// />
|
/>
|
||||||
// </svg>
|
</svg>
|
||||||
// </button>
|
</button>
|
||||||
// </span>
|
</span>
|
||||||
// ))}
|
))}
|
||||||
// </div>
|
</div>
|
||||||
// <input
|
<Input
|
||||||
// value={searchText}
|
value={searchText}
|
||||||
// onKeyDown={handleKeyPress}
|
onKeyDown={handleKeyPress}
|
||||||
// onChange={handleChange}
|
onChange={handleChange}
|
||||||
// placeholder="Start typing and press enter"
|
placeholder="Start typing and press enter"
|
||||||
// className="input w-full focus:outline-none"
|
/>
|
||||||
// />
|
</div>
|
||||||
// </div>
|
{isSearching && (
|
||||||
// {isSearching && (
|
<div role="status" className="z-50 -mt-3 ml-5">
|
||||||
// <div role="status" className="z-50 -mt-3 ml-5">
|
<svg
|
||||||
// <svg
|
aria-hidden="true"
|
||||||
// aria-hidden="true"
|
className="mr-2 h-4 w-4"
|
||||||
// className="mr-2 h-4 w-4"
|
viewBox="0 0 100 101"
|
||||||
// viewBox="0 0 100 101"
|
fill="none"
|
||||||
// fill="none"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
// xmlns="http://www.w3.org/2000/svg"
|
>
|
||||||
// >
|
<path
|
||||||
// <path
|
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
||||||
// d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
fill="currentColor"
|
||||||
// fill="currentColor"
|
/>
|
||||||
// />
|
<path
|
||||||
// <path
|
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
||||||
// d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
fill="currentFill"
|
||||||
// fill="currentFill"
|
/>
|
||||||
// />
|
</svg>
|
||||||
// </svg>
|
<span className="sr-only">Loading...</span>
|
||||||
// <span className="sr-only">Loading...</span>
|
</div>
|
||||||
// </div>
|
)}
|
||||||
// )}
|
{searchResults && searchResults.length !== 0 && (
|
||||||
// {searchResults && searchResults.length !== 0 && (
|
<div className={`z-50 mb-4 flex space-y-0`}>
|
||||||
// <div className={`z-50 mb-4 flex space-y-0`}>
|
<aside
|
||||||
// <aside
|
aria-labelledby="menu-heading"
|
||||||
// aria-labelledby="menu-heading"
|
className="absolute z-50 -mt-5 flex w-64 flex-col items-start rounded-md border bg-white text-sm shadow-md"
|
||||||
// className="absolute z-50 -mt-5 flex w-64 flex-col items-start rounded-md border bg-white text-sm shadow-md"
|
>
|
||||||
// >
|
<ul className="flex w-full flex-col">
|
||||||
// <ul className="flex w-full flex-col">
|
{searchResults.map((result) => (
|
||||||
// {searchResults.map((result) => (
|
<li
|
||||||
// <li
|
key={result}
|
||||||
// key={result}
|
onClick={doResultClick}
|
||||||
// onClick={doResultClick}
|
className="cursor-pointer space-x-2 px-2 py-1 hover:bg-indigo-500 hover:text-white focus:bg-indigo-500 focus:text-white focus:outline-none"
|
||||||
// className="cursor-pointer space-x-2 px-2 py-1 hover:bg-indigo-500 hover:text-white focus:bg-indigo-500 focus:text-white focus:outline-none"
|
>
|
||||||
// >
|
{result}
|
||||||
// {result}
|
</li>
|
||||||
// </li>
|
))}
|
||||||
// ))}
|
</ul>
|
||||||
// </ul>
|
</aside>
|
||||||
// </aside>
|
</div>
|
||||||
// </div>
|
)}
|
||||||
// )}
|
</>
|
||||||
// </>
|
);
|
||||||
// );
|
};
|
||||||
// };
|
|
||||||
|
|
||||||
// export default TaggedInput;
|
export default TaggedInput;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { Fragment } from "react";
|
import React, { Fragment } from "react";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
import { Menu, Transition } from "@headlessui/react";
|
import { Menu, Transition } from "@headlessui/react";
|
||||||
import { signOut } from "next-auth/react";
|
import { signOut } from "next-auth/react";
|
||||||
import { logger } from "@/lib/logger";
|
import { logger } from "@/lib/logger";
|
||||||
@@ -9,10 +11,11 @@ interface IUserNavDropdownProps {
|
|||||||
session: Session | null;
|
session: Session | null;
|
||||||
}
|
}
|
||||||
const UserNavDropdown: React.FC<IUserNavDropdownProps> = ({ session }) => {
|
const UserNavDropdown: React.FC<IUserNavDropdownProps> = ({ session }) => {
|
||||||
const [profileImage, setProfileImage] = React.useState<string>();
|
const [profileImage, setProfileImage] = React.useState<string>(
|
||||||
|
session?.user?.image || "/images/default-profile.jpg",
|
||||||
|
);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
logger.debug("UserNavDropdown", "session", session);
|
logger.debug("UserNavDropdown", "session", session);
|
||||||
|
|
||||||
setProfileImage(session?.user?.image || "/images/default-profile.jpg");
|
setProfileImage(session?.user?.image || "/images/default-profile.jpg");
|
||||||
}, [session]);
|
}, [session]);
|
||||||
|
|
||||||
@@ -22,11 +25,15 @@ const UserNavDropdown: React.FC<IUserNavDropdownProps> = ({ session }) => {
|
|||||||
<div>
|
<div>
|
||||||
<Menu.Button className="flex rounded-full bg-gray-800 text-sm text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800">
|
<Menu.Button className="flex rounded-full bg-gray-800 text-sm text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800">
|
||||||
<span className="sr-only">Open user menu</span>
|
<span className="sr-only">Open user menu</span>
|
||||||
<img
|
{profileImage && (
|
||||||
className="h-8 w-8 rounded-full"
|
<Image
|
||||||
src={profileImage}
|
width={32}
|
||||||
alt="Profile image"
|
height={32}
|
||||||
/>
|
className="h-8 w-8 rounded-full"
|
||||||
|
src={profileImage}
|
||||||
|
alt="Profile image"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Menu.Button>
|
</Menu.Button>
|
||||||
</div>
|
</div>
|
||||||
<Transition
|
<Transition
|
||||||
|
|||||||
@@ -72,5 +72,7 @@ export function TRPCReactProvider(props: { children: React.ReactNode }) {
|
|||||||
function getBaseUrl() {
|
function getBaseUrl() {
|
||||||
if (typeof window !== "undefined") return window.location.origin;
|
if (typeof window !== "undefined") return window.location.origin;
|
||||||
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
|
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
|
||||||
|
if (process.env.NEXT_PUBLIC_SITE_URL)
|
||||||
|
return `https://${process.env.NEXT_PUBLIC_SITE_URL}`;
|
||||||
return `http://localhost:${process.env.PORT ?? 3000}`;
|
return `http://localhost:${process.env.PORT ?? 3000}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,84 +1,80 @@
|
|||||||
import { type Config } from "tailwindcss";
|
import { type Config } from "tailwindcss";
|
||||||
import { fontFamily } from "tailwindcss/defaultTheme";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
darkMode: ["class"],
|
darkMode: ["class"],
|
||||||
content: ["./src/**/*.tsx"],
|
content: ["./src/**/*.tsx"],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
fontFamily: {
|
borderRadius: {
|
||||||
sans: ["var(--font-geist-sans)", ...fontFamily.sans]
|
lg: "var(--radius)",
|
||||||
},
|
md: "calc(var(--radius) - 2px)",
|
||||||
borderRadius: {
|
sm: "calc(var(--radius) - 4px)",
|
||||||
lg: 'var(--radius)',
|
},
|
||||||
md: 'calc(var(--radius) - 2px)',
|
colors: {
|
||||||
sm: 'calc(var(--radius) - 4px)'
|
background: "hsl(var(--background))",
|
||||||
},
|
foreground: "hsl(var(--foreground))",
|
||||||
colors: {
|
card: {
|
||||||
background: 'hsl(var(--background))',
|
DEFAULT: "hsl(var(--card))",
|
||||||
foreground: 'hsl(var(--foreground))',
|
foreground: "hsl(var(--card-foreground))",
|
||||||
card: {
|
},
|
||||||
DEFAULT: 'hsl(var(--card))',
|
popover: {
|
||||||
foreground: 'hsl(var(--card-foreground))'
|
DEFAULT: "hsl(var(--popover))",
|
||||||
},
|
foreground: "hsl(var(--popover-foreground))",
|
||||||
popover: {
|
},
|
||||||
DEFAULT: 'hsl(var(--popover))',
|
primary: {
|
||||||
foreground: 'hsl(var(--popover-foreground))'
|
DEFAULT: "hsl(var(--primary))",
|
||||||
},
|
foreground: "hsl(var(--primary-foreground))",
|
||||||
primary: {
|
},
|
||||||
DEFAULT: 'hsl(var(--primary))',
|
secondary: {
|
||||||
foreground: 'hsl(var(--primary-foreground))'
|
DEFAULT: "hsl(var(--secondary))",
|
||||||
},
|
foreground: "hsl(var(--secondary-foreground))",
|
||||||
secondary: {
|
},
|
||||||
DEFAULT: 'hsl(var(--secondary))',
|
muted: {
|
||||||
foreground: 'hsl(var(--secondary-foreground))'
|
DEFAULT: "hsl(var(--muted))",
|
||||||
},
|
foreground: "hsl(var(--muted-foreground))",
|
||||||
muted: {
|
},
|
||||||
DEFAULT: 'hsl(var(--muted))',
|
accent: {
|
||||||
foreground: 'hsl(var(--muted-foreground))'
|
DEFAULT: "hsl(var(--accent))",
|
||||||
},
|
foreground: "hsl(var(--accent-foreground))",
|
||||||
accent: {
|
},
|
||||||
DEFAULT: 'hsl(var(--accent))',
|
destructive: {
|
||||||
foreground: 'hsl(var(--accent-foreground))'
|
DEFAULT: "hsl(var(--destructive))",
|
||||||
},
|
foreground: "hsl(var(--destructive-foreground))",
|
||||||
destructive: {
|
},
|
||||||
DEFAULT: 'hsl(var(--destructive))',
|
border: "hsl(var(--border))",
|
||||||
foreground: 'hsl(var(--destructive-foreground))'
|
input: "hsl(var(--input))",
|
||||||
},
|
ring: "hsl(var(--ring))",
|
||||||
border: 'hsl(var(--border))',
|
chart: {
|
||||||
input: 'hsl(var(--input))',
|
"1": "hsl(var(--chart-1))",
|
||||||
ring: 'hsl(var(--ring))',
|
"2": "hsl(var(--chart-2))",
|
||||||
chart: {
|
"3": "hsl(var(--chart-3))",
|
||||||
'1': 'hsl(var(--chart-1))',
|
"4": "hsl(var(--chart-4))",
|
||||||
'2': 'hsl(var(--chart-2))',
|
"5": "hsl(var(--chart-5))",
|
||||||
'3': 'hsl(var(--chart-3))',
|
},
|
||||||
'4': 'hsl(var(--chart-4))',
|
},
|
||||||
'5': 'hsl(var(--chart-5))'
|
keyframes: {
|
||||||
}
|
"accordion-down": {
|
||||||
},
|
from: {
|
||||||
keyframes: {
|
height: "0",
|
||||||
'accordion-down': {
|
},
|
||||||
from: {
|
to: {
|
||||||
height: '0'
|
height: "var(--radix-accordion-content-height)",
|
||||||
},
|
},
|
||||||
to: {
|
},
|
||||||
height: 'var(--radix-accordion-content-height)'
|
"accordion-up": {
|
||||||
}
|
from: {
|
||||||
},
|
height: "var(--radix-accordion-content-height)",
|
||||||
'accordion-up': {
|
},
|
||||||
from: {
|
to: {
|
||||||
height: 'var(--radix-accordion-content-height)'
|
height: "0",
|
||||||
},
|
},
|
||||||
to: {
|
},
|
||||||
height: '0'
|
},
|
||||||
}
|
animation: {
|
||||||
}
|
"accordion-down": "accordion-down 0.2s ease-out",
|
||||||
},
|
"accordion-up": "accordion-up 0.2s ease-out",
|
||||||
animation: {
|
},
|
||||||
'accordion-down': 'accordion-down 0.2s ease-out',
|
},
|
||||||
'accordion-up': 'accordion-up 0.2s ease-out'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
plugins: [require("tailwindcss-animate")],
|
plugins: [require("tailwindcss-animate")],
|
||||||
} satisfies Config;
|
} satisfies Config;
|
||||||
|
|||||||
Reference in New Issue
Block a user