mirror of
https://github.com/hanzoai/captable.git
synced 2026-10-16 14:21:29 +00:00
* Configure VS Code settings for ESLint and TypeScript to automatically organize imports and remove unused imports * Fix SVG attribute names in icons.tsx * Add logo upload functionality to CompanyForm component
This commit is contained in:
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
{ "language": "typescript", "autoFix": true },
|
||||
{ "language": "typescriptreact", "autoFix": true }
|
||||
],
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"editor.tabCompletion": "on",
|
||||
"editor.snippetSuggestions": "top",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "explicit",
|
||||
"source.fixAll": "explicit"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
"use client";
|
||||
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -10,9 +10,7 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { RiArrowRightLine, RiImageCircleFill } from "@remixicon/react";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -20,21 +18,26 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import {
|
||||
ZodOnboardingMutationSchema,
|
||||
type TypeZodOnboardingMutationSchema,
|
||||
} from "@/trpc/routers/onboarding-router/schema";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { RiArrowRightLine } from "@remixicon/react";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { dayjsExt } from "@/common/dayjs";
|
||||
import { uploadFile } from "@/common/uploads";
|
||||
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import countries from "@/lib/countries";
|
||||
import { cn, isFileExists, validateFile } from "@/lib/utils";
|
||||
import { api } from "@/trpc/react";
|
||||
import { type RouterOutputs } from "@/trpc/shared";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import countries from "@/lib/countries";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { type RouterOutputs } from "@/trpc/shared";
|
||||
import { dayjsExt } from "@/common/dayjs";
|
||||
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
||||
import { useRef, useState } from "react";
|
||||
import Loading from "../shared/loading";
|
||||
|
||||
const formSchema = ZodOnboardingMutationSchema;
|
||||
|
||||
@@ -53,9 +56,12 @@ type CompanyFormProps =
|
||||
};
|
||||
|
||||
export const CompanyForm = ({ type, data }: CompanyFormProps) => {
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const { update, data: user } = useSession();
|
||||
const router = useRouter();
|
||||
const { toast } = useToast();
|
||||
const [imageUrl, setImageUrl] = useState<string>(data?.company.logo ?? "");
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const form = useForm<TypeZodOnboardingMutationSchema>({
|
||||
resolver: zodResolver(formSchema),
|
||||
@@ -105,6 +111,82 @@ export const CompanyForm = ({ type, data }: CompanyFormProps) => {
|
||||
},
|
||||
});
|
||||
|
||||
async function handleLogoUpload(file: File): Promise<{ imageUrl: string }> {
|
||||
if (user?.user.id) {
|
||||
const options = {
|
||||
expiresIn: 3600,
|
||||
keyPrefix: "company-logo",
|
||||
identifier: user.user.id,
|
||||
};
|
||||
const { fileUrl } = await uploadFile(file, options, "publicBucket");
|
||||
setImageUrl(fileUrl);
|
||||
|
||||
return { imageUrl: fileUrl };
|
||||
}
|
||||
|
||||
return { imageUrl: "" };
|
||||
}
|
||||
|
||||
const handleLogoChangeAndUpload = async (
|
||||
event: React.ChangeEvent<HTMLInputElement>,
|
||||
) => {
|
||||
const { file } = isFileExists(event);
|
||||
|
||||
if (!file) return;
|
||||
|
||||
const { isValid, title, errorMessage } = validateFile(file);
|
||||
|
||||
if (isValid) {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { imageUrl } = await handleLogoUpload(file);
|
||||
|
||||
if (!imageUrl) {
|
||||
return toast({
|
||||
variant: "destructive",
|
||||
title: "Failed uploading the logo.",
|
||||
description: "Please try again later.",
|
||||
});
|
||||
}
|
||||
|
||||
const currentValues = form.getValues();
|
||||
|
||||
companySettingMutation.mutate({
|
||||
user: {
|
||||
name: currentValues.user.name,
|
||||
email: currentValues.user.email,
|
||||
title: currentValues.user.title,
|
||||
},
|
||||
company: {
|
||||
...currentValues.company,
|
||||
logo: imageUrl,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Something went wrong", error);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Failed uploading logo.",
|
||||
description: "Please try again later.",
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
} else {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: title,
|
||||
description: errorMessage,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogoChange = () => {
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.click();
|
||||
}
|
||||
};
|
||||
|
||||
async function onSubmit(values: TypeZodOnboardingMutationSchema) {
|
||||
try {
|
||||
if (type === "create" || type === "onboarding") {
|
||||
@@ -124,18 +206,32 @@ export const CompanyForm = ({ type, data }: CompanyFormProps) => {
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
<div className="col-span-full flex items-center gap-x-8">
|
||||
<Avatar className="h-20 w-20 rounded">
|
||||
<AvatarImage src={"/placeholders/company.svg"} />
|
||||
<AvatarImage src={imageUrl || "/placeholders/company.svg"} />
|
||||
</Avatar>
|
||||
|
||||
<div>
|
||||
<Button size="sm" variant={"outline"} type="button">
|
||||
{type === "edit" ? "Change company logo" : "Upload company logo"}
|
||||
</Button>
|
||||
<p className="mt-2 text-xs text-gray-700">
|
||||
JPG, GIF or PNG. 1MB max.
|
||||
</p>
|
||||
<div className="flex items-start space-x-3">
|
||||
<div>
|
||||
<Button
|
||||
onClick={handleLogoChange}
|
||||
size="sm"
|
||||
variant={"outline"}
|
||||
type="button"
|
||||
>
|
||||
Change company logo
|
||||
</Button>
|
||||
<Input
|
||||
className="hidden"
|
||||
type="file"
|
||||
onChange={handleLogoChangeAndUpload}
|
||||
ref={fileInputRef}
|
||||
/>
|
||||
<p className="mt-2 text-xs text-gray-700">
|
||||
JPG, GIF or PNG. 1MB max.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<div className="grid gap-5">
|
||||
{type === "onboarding" && (
|
||||
@@ -383,6 +479,8 @@ export const CompanyForm = ({ type, data }: CompanyFormProps) => {
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{loading && <Loading />}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -32,14 +32,14 @@ export const LogoIcon = (props: IconProps) => {
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clip-path="url(#clip0_47_2)">
|
||||
<g clipPath="url(#clip0_47_2)">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M712.256 270.067C704.618 283.9 687.213 288.922 673.38 281.285C565.3 221.612 426.629 237.659 335.002 329.285C223.967 440.32 223.968 620.342 335.002 731.375C426.629 823.003 565.3 839.051 673.381 779.378C687.215 771.739 704.62 776.761 712.257 790.597C719.894 804.428 714.872 821.834 701.038 829.473C571.242 901.134 404.674 881.971 294.54 771.837C161.16 638.457 161.159 422.204 294.54 288.823C404.672 178.691 571.239 159.527 701.038 231.191C714.871 238.829 719.893 256.234 712.256 270.067Z"
|
||||
fill="#090000"
|
||||
stroke="#090000"
|
||||
stroke-width="40"
|
||||
strokeWidth="40"
|
||||
/>
|
||||
<path
|
||||
d="M820.303 351.98L819.748 351.329C817.336 348.495 813.975 344.546 810.463 341.507C805.819 337.487 798.764 333.067 789.033 332.68C779.007 332.285 771.505 336.441 766.714 340.016C762.966 342.813 759.236 346.55 756.445 349.348L615.807 489.986C610.966 494.823 605.731 500.054 601.908 505.065C597.429 510.935 592.608 519.335 592.607 530.461C592.608 541.588 597.427 549.988 601.906 555.859C605.73 560.87 610.965 566.1 615.807 570.938L756.445 711.576C759.237 714.373 762.967 718.112 766.715 720.909C771.506 724.48 779.009 728.639 789.035 728.243C798.764 727.86 805.819 723.436 810.463 719.418C813.976 716.378 817.335 712.43 819.747 709.595L820.305 708.945C907.887 606.306 907.886 454.618 820.303 351.98Z"
|
||||
|
||||
@@ -29,6 +29,7 @@ export const companyRouter = createTRPCRouter({
|
||||
city: true,
|
||||
zipcode: true,
|
||||
streetAddress: true,
|
||||
logo: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -46,6 +46,7 @@ export const ZodCompanyMutationSchema = z.object({
|
||||
zipcode: z.string().min(1, {
|
||||
message: "Zipcode is required",
|
||||
}),
|
||||
logo: z.string().min(1).optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user