feat: stage two

This commit is contained in:
Nevo David 2024-04-10 18:18:03 +07:00
parent de29aa977a
commit ad6d4941e1
14 changed files with 412 additions and 14 deletions

View File

@ -0,0 +1,16 @@
import { Buyer } from '@gitroom/frontend/components/marketplace/buyer';
export const dynamic = 'force-dynamic';
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Gitroom Marketplace',
description: '',
};
export default async function Index({
searchParams,
}: {
searchParams: { code: string };
}) {
return <Buyer />;
}

View File

@ -0,0 +1,11 @@
import { BuyerSeller } from '@gitroom/frontend/components/marketplace/buyer.seller';
import { ReactNode } from 'react';
export default function Layout({ children }: { children: ReactNode }) {
return (
<>
<BuyerSeller />
{children}
</>
)
}

View File

@ -0,0 +1,17 @@
export const dynamic = 'force-dynamic';
import { Metadata } from 'next';
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
export const metadata: Metadata = {
title: 'Gitroom Marketplace',
description: '',
};
export default async function Index({
searchParams,
}: {
searchParams: { code: string };
}) {
const currentCookie = cookies()?.get('marketplace')?.value;
return redirect(currentCookie === 'buyer' ? '/marketplace/buyer' : '/marketplace/seller');
}

View File

@ -0,0 +1,16 @@
import { Seller } from '@gitroom/frontend/components/marketplace/seller';
export const dynamic = 'force-dynamic';
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Gitroom Marketplace',
description: '',
};
export default async function Index({
searchParams,
}: {
searchParams: { code: string };
}) {
return <Seller />;
}

View File

@ -7,7 +7,7 @@ import {menuItems} from "@gitroom/frontend/components/layout/top.menu";
export const Title = () => {
const path = usePathname();
const currentTitle = useMemo(() => {
return menuItems.find(item => item.path === path)?.name;
return menuItems.find(item => path.indexOf(item.path) > -1)?.name;
}, [path]);
return (

View File

@ -29,6 +29,11 @@ export const menuItems = [
path: '/billing',
role: ['ADMIN', 'SUPERADMIN'],
},
{
name: 'Marketplace',
icon: 'marketplace',
path: '/marketplace',
},
];
export const TopMenu: FC = () => {
@ -59,8 +64,8 @@ export const TopMenu: FC = () => {
}
return true;
})
.map((p) => p.path)
.indexOf(path) === index
.map((p) => path.indexOf(p.path) > -1 ? index : -1)
.indexOf(index) === index
? 'text-primary showbox'
: 'text-gray'
)}

View File

@ -0,0 +1,20 @@
"use client";
import { FC } from 'react';
import { usePathname } from 'next/navigation';
import clsx from 'clsx';
import Link from 'next/link';
export const BuyerSeller: FC = () => {
const path = usePathname();
const pathComputed = path === '/marketplace' ? '/marketplace/seller' : path;
return (
<div className="relative">
<div className="w-[286px] h-[50px] bg-third p-[9px] flex select-none absolute -translate-y-[63px] right-0">
<div className="bg-input flex flex-1">
<Link href="/marketplace/seller" className={clsx("flex justify-center items-center flex-1", pathComputed.indexOf('/marketplace/seller') > -1 && 'bg-forth')}>Seller</Link>
<Link href="/marketplace/buyer" className={clsx("flex justify-center items-center flex-1", pathComputed.indexOf('/marketplace/buyer') > -1 && 'bg-forth')}>Buyer</Link>
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,235 @@
'use client';
import { FC, useCallback, useRef, useState } from 'react';
import { Checkbox } from '@gitroom/react/form/checkbox';
import { useRouter, useSearchParams } from 'next/navigation';
import clsx from 'clsx';
import interClass from '@gitroom/react/helpers/inter.font';
import { Button } from '@gitroom/react/form/button';
export const LabelCheckbox: FC<{
label: string;
name: string;
value: string;
checked: boolean;
onChange: (value: string, status: boolean) => void;
}> = (props) => {
const { label, name, value, checked, onChange } = props;
const ref = useRef<any>(null);
const [innerCheck, setInnerCheck] = useState(checked);
const change = useCallback(() => {
setInnerCheck(!innerCheck);
onChange(value, !innerCheck);
}, [innerCheck]);
return (
<div className="flex items-center gap-[10px] select-none">
<Checkbox
ref={ref}
variant="hollow"
name={name}
checked={checked}
onChange={change}
disableForm={true}
/>
<label
onClick={() => ref.current.click()}
className="text-[20px]"
htmlFor={name}
>
{label}
</label>
</div>
);
};
export const Options: FC<{
title: string;
options: Array<{ key: string; value: string }>;
query: string;
}> = (props) => {
const { title, options, query } = props;
const router = useRouter();
const searchParams = (useSearchParams().get(query) || '')?.split(',') || [];
const change = (value: string, state: boolean) => {
const getAll = new URLSearchParams(window.location.search).get(query);
const splitAll = (getAll?.split(',') || []).filter((f) => f);
if (state) {
splitAll?.push(value);
} else {
splitAll?.splice(splitAll.indexOf(value), 1);
}
const params = new URLSearchParams(window.location.search);
if (!splitAll?.length) {
params.delete(query);
} else {
params.set(query, splitAll?.join(',') || '');
}
router.replace('?' + params.toString());
return params.toString();
};
return (
<>
<div className="h-[56px] text-[20px] font-[600] flex items-center px-[24px] bg-[#0F1524]">
{title}
</div>
<div className="bg-[#0b0f1c] flex flex-col gap-[16px] px-[32px] py-[24px]">
{options.map((option) => (
<div key={option.key} className="flex items-center gap-[10px]">
<LabelCheckbox
value={option.key}
label={option.value}
checked={searchParams.indexOf(option.key) > -1}
name={query}
onChange={change}
/>
</div>
))}
</div>
</>
);
};
export const Card = () => {
return (
<div className="min-h-[155px] bg-[#0B101B] p-[24px] flex">
<div className="flex gap-[16px] flex-1">
<div>
<div className="h-[103px] w-[103px] bg-red-500/10 rounded-full relative">
<img
src="https://via.placeholder.com/103"
className="rounded-full w-full h-full"
/>
<div className="w-[80px] h-[28px] bg-[#8155DD] absolute bottom-0 left-[50%] -translate-x-[50%] rounded-[30px] flex gap-[4px] justify-center items-center">
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<path
d="M7.82348 9.80876C8.45164 9.28076 8.90221 8.57235 9.11409 7.77958C9.32597 6.98682 9.28891 6.14807 9.00792 5.37709C8.72694 4.60611 8.21563 3.9402 7.54334 3.46967C6.87106 2.99914 6.07032 2.74677 5.24973 2.74677C4.42914 2.74677 3.62841 2.99914 2.95612 3.46967C2.28383 3.9402 1.77253 4.60611 1.49154 5.37709C1.21056 6.14807 1.17349 6.98682 1.38537 7.77958C1.59725 8.57235 2.04782 9.28076 2.67598 9.80876C1.69509 10.2523 0.845054 10.9411 0.207856 11.8088C0.0901665 11.9691 0.0410049 12.1697 0.0711866 12.3663C0.101368 12.5629 0.208421 12.7395 0.368794 12.8572C0.529167 12.9749 0.729724 13.024 0.926344 12.9939C1.12296 12.9637 1.29954 12.8566 1.41723 12.6963C1.85832 12.0938 2.43521 11.6039 3.10109 11.2662C3.76697 10.9284 4.50309 10.7524 5.24973 10.7524C5.99637 10.7524 6.73249 10.9284 7.39837 11.2662C8.06426 11.6039 8.64114 12.0938 9.08223 12.6963C9.19992 12.8567 9.37653 12.9638 9.57321 12.9941C9.76989 13.0243 9.97053 12.9752 10.131 12.8575C10.2914 12.7398 10.3986 12.5632 10.4288 12.3665C10.459 12.1699 10.4099 11.9692 10.2922 11.8088C9.65465 10.9412 8.80445 10.2524 7.82348 9.80876ZM2.74973 6.75001C2.74973 6.25556 2.89635 5.77221 3.17106 5.36108C3.44576 4.94996 3.83621 4.62953 4.29302 4.44031C4.74984 4.25109 5.2525 4.20158 5.73746 4.29805C6.22241 4.39451 6.66787 4.63261 7.0175 4.98224C7.36713 5.33187 7.60523 5.77733 7.70169 6.26228C7.79816 6.74724 7.74865 7.2499 7.55943 7.70672C7.37021 8.16353 7.04978 8.55398 6.63866 8.82868C6.22753 9.10339 5.74418 9.25001 5.24973 9.25001C4.58669 9.25001 3.95081 8.98662 3.48196 8.51778C3.01312 8.04894 2.74973 7.41305 2.74973 6.75001ZM15.631 12.8544C15.5516 12.9127 15.4615 12.9549 15.3658 12.9784C15.2701 13.0019 15.1707 13.0063 15.0733 12.9914C14.9759 12.9765 14.8824 12.9425 14.7982 12.8914C14.7139 12.8404 14.6405 12.7732 14.5822 12.6938C14.1401 12.0925 13.5629 11.6034 12.8973 11.2658C12.2317 10.9282 11.4961 10.7515 10.7497 10.75C10.5508 10.75 10.3601 10.671 10.2194 10.5303C10.0787 10.3897 9.99973 10.1989 9.99973 10C9.99973 9.8011 10.0787 9.61033 10.2194 9.46968C10.3601 9.32903 10.5508 9.25001 10.7497 9.25001C11.1178 9.24958 11.4813 9.16786 11.8142 9.01071C12.147 8.85355 12.4411 8.62482 12.6753 8.34086C12.9096 8.05691 13.0782 7.72473 13.1692 7.36805C13.2602 7.01138 13.2713 6.63901 13.2017 6.27754C13.1322 5.91607 12.9837 5.57443 12.7668 5.277C12.5499 4.97958 12.27 4.73373 11.9471 4.557C11.6242 4.38027 11.2662 4.27702 10.8988 4.25464C10.5314 4.23225 10.1636 4.29128 9.82161 4.42751C9.73 4.46502 9.63188 4.48402 9.5329 4.48343C9.43392 4.48283 9.33603 4.46265 9.24488 4.42404C9.15374 4.38543 9.07114 4.32916 9.00184 4.25848C8.93255 4.18779 8.87793 4.10409 8.84114 4.0122C8.80435 3.9203 8.78612 3.82204 8.78749 3.72306C8.78885 3.62409 8.8098 3.52636 8.84912 3.43552C8.88844 3.34468 8.94535 3.26252 9.01658 3.19378C9.0878 3.12504 9.17193 3.07108 9.26411 3.03501C10.1468 2.6816 11.1265 2.65418 12.0276 2.95767C12.9287 3.26115 13.6922 3.87569 14.1812 4.6911C14.6703 5.50652 14.8528 6.46947 14.6962 7.4073C14.5396 8.34512 14.0541 9.1965 13.3266 9.80876C14.3075 10.2523 15.1575 10.9411 15.7947 11.8088C15.9113 11.9693 15.9594 12.1694 15.9288 12.3654C15.8981 12.5613 15.791 12.7372 15.631 12.8544Z"
fill="white"
/>
</svg>
</div>
<div className="text-[14px]">22,6K</div>
</div>
</div>
</div>
<div className="flex flex-col gap-[8px]">
<div className="flex gap-[14px] items-center">
<div className="text-[24px]">Nevo David</div>
<div className="flex gap-[3px]">
<div
className={clsx(
'bg-[#172034] rounded-[34px] py-[8px] px-[12px] text-[12px]',
interClass
)}
>
Content Writer
</div>
<div
className={clsx(
'bg-[#172034] rounded-[34px] py-[8px] px-[12px] text-[12px]',
interClass
)}
>
Influencer
</div>
</div>
<div className="flex gap-[10px]">
<img
src="/icons/platforms/devto.png"
className="w-[24px] h-[24px] rounded-full"
/>
<img
src="/icons/platforms/hashnode.png"
className="w-[24px] h-[24px] rounded-full"
/>
<img
src="/icons/platforms/linkedin.png"
className="w-[24px] h-[24px] rounded-full"
/>
</div>
</div>
<div className="text-[18px] text-[#AAA] font-[400]">
Maecenas dignissim justo eget nulla rutrum molestie. Maecenas
lobortis sem dui,
</div>
<div
className={clsx(
'gap-[8px] flex items-center text-[10px] font-[300] text-[#CEBDF2] tracking-[1.2px] uppercase',
interClass
)}
>
<div>AI</div>
<div>
<div className="w-[4px] h-[4px] bg-[#324264] rounded-full" />
</div>
<div>AI</div>
<div>
<div className="w-[4px] h-[4px] bg-[#324264] rounded-full" />
</div>
<div>AI</div>
</div>
</div>
</div>
<div className="ml-[100px] items-center flex">
<Button>Request Service</Button>
</div>
</div>
);
};
export const Buyer = () => {
return (
<div className="flex mt-[29px] w-full gap-[43px]">
<div className="w-[330px]">
<div className="flex flex-col gap-[16px]">
<h2 className="text-[20px]">Filter</h2>
<div className="flex flex-col">
<Options
options={[
{ key: 'asd', value: 'asdfasdf' },
{ key: 'asggggd', value: 'asdfassdfgsdfgdf' },
]}
query="bla"
title="hello"
/>
<Options
options={[
{ key: 'asd', value: 'asdfasdf' },
{ key: 'asggggd', value: 'asdfassdfgsdfgdf' },
]}
query="blassss"
title="sdfgsdgdsfg"
/>
</div>
</div>
</div>
<div className="flex-1 gap-[16px] flex-col flex">
<div className="text-[20px] text-right">234 Result</div>
<Card />
<Card />
<Card />
<Card />
<Card />
<Card />
<Card />
<Card />
<Card />
<Card />
<Card />
</div>
</div>
);
};

View File

@ -0,0 +1,3 @@
export const Marketplace = () => {
return <div />
}

View File

@ -0,0 +1,32 @@
'use client';
import { Slider } from "@gitroom/react/form/slider";
import { Button } from '@gitroom/react/form/button';
export const Seller = () => {
return (
<div className="flex mt-[29px] w-full gap-[26px]">
<div className="w-[328px] flex flex-col gap-[16px]">
<h2 className="text-[20px]">Seller Mode</h2>
<div className="flex p-[24px] bg-sixth rounded-[4px] border border-[#172034] flex-col items-center gap-[16px]">
<div className="w-[64px] h-[64px] bg-[#D9D9D9] rounded-full" />
<div className="text-[24px]">John Smith</div>
<div className="flex gap-[16px] items-center pb-[8px]">
<Slider fill={true} value="on" onChange={() => {}} />
<div className="text-[18px]">Active</div>
</div>
<div className="border-t border-t-[#425379] w-full" />
<div className="w-full">
<Button className="w-full">Connect Bank Account</Button>
</div>
</div>
</div>
<div className="flex-1 flex gap-[16px] flex-col">
<h2 className="text-[20px]">Details</h2>
<div className="bg-sixth p-[24px] rounded-[4px] border border-[#172034]">
asdfasdf
</div>
</div>
</div>
);
};

View File

@ -90,7 +90,26 @@ export async function middleware(request: NextRequest) {
return NextResponse.redirect(new URL(`/analytics`, nextUrl.href));
}
return NextResponse.next();
const next = NextResponse.next();
if (
nextUrl.pathname === '/marketplace/seller' ||
nextUrl.pathname === '/marketplace/buyer'
) {
const type = nextUrl.pathname.split('/marketplace/')[1].split('/')[0];
next.cookies.set('marketplace', type === 'seller' ? 'seller' : 'buyer', {
path: '/',
sameSite: false,
httpOnly: true,
secure: true,
expires: new Date(Date.now() + 15 * 60 * 1000),
domain:
'.' + new URL(removeSubdomain(process.env.FRONTEND_URL!)).hostname,
});
}
return next;
} catch (err) {
return NextResponse.redirect(new URL('/auth/logout', nextUrl.href));
}

View File

@ -32,6 +32,10 @@ model User {
email String
password String?
providerName Provider
name String?
lastName String?
pictureId String?
picture Media? @relation(fields: [pictureId], references: [id])
providerId String?
organizations UserOrganization[]
timezone Int
@ -40,6 +44,7 @@ model User {
updatedAt DateTime @updatedAt
lastReadNotifications DateTime @default(now())
inviteId String?
tagUser TagUser[]
@@unique([email, providerName])
@@index([lastReadNotifications])
@ -94,6 +99,21 @@ model TrendingLog {
date DateTime
}
model TagUser {
id String @id @default(uuid())
user User @relation(fields: [userId], references: [id])
userId String
tagOptions TagOptions @relation(fields: [tagOptionsId], references: [id])
tagOptionsId String
}
model TagOptions {
id String @id @default(uuid())
key String
value String
tagUser TagUser[]
}
model Star {
id String @id @default(uuid())
stars Int
@ -116,6 +136,7 @@ model Media {
organizationId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userPicture User[]
@@index([organizationId])
}

View File

@ -1,17 +1,18 @@
'use client';
import { FC, useCallback, useState } from 'react';
import { FC, forwardRef, useCallback, useState } from 'react';
import clsx from 'clsx';
import Image from 'next/image';
import { useFormContext, useWatch } from 'react-hook-form';
export const Checkbox: FC<{
export const Checkbox = forwardRef<null, {
checked?: boolean;
disableForm?: boolean;
name?: string;
className?: string;
onChange?: (event: { target: { name?: string, value: boolean } }) => void;
}> = (props) => {
const { checked, className, disableForm } = props;
variant?: 'default' | 'hollow';
}>((props, ref: any) => {
const { checked, className, disableForm, variant } = props;
const form = useFormContext();
const register = disableForm ? {} : form.register(props.name!);
@ -31,10 +32,12 @@ export const Checkbox: FC<{
return (
<div
ref={ref}
{...register}
onClick={changeStatus}
className={clsx(
'cursor-pointer rounded-[4px] select-none bg-forth w-[24px] h-[24px] flex justify-center items-center',
'cursor-pointer rounded-[4px] select-none w-[24px] h-[24px] justify-center items-center flex',
variant === 'default' || !variant ? ('bg-forth') : ('border-[#324264] border-2 bg-[#141c2c]'),
className
)}
>
@ -43,4 +46,4 @@ export const Checkbox: FC<{
)}
</div>
);
};
});

View File

@ -3,17 +3,17 @@
import {FC, useCallback} from "react";
import clsx from "clsx";
export const Slider: FC<{value: 'on' | 'off', onChange: (value: 'on' | 'off') => void}> = (props) => {
const {value, onChange} = props;
export const Slider: FC<{value: 'on' | 'off', fill?: boolean, onChange: (value: 'on' | 'off') => void}> = (props) => {
const {value, onChange, fill} = props;
const change = useCallback(() => {
onChange(value === 'on' ? 'off' : 'on');
}, [value]);
return (
<div className="w-[57px] h-[32px] p-[4px] border-fifth border rounded-[100px]" onClick={change}>
<div className={clsx("w-[57px] h-[34px] p-[4px] border-fifth border rounded-[100px]", value === 'on' && fill && 'bg-[#8155DD]')} onClick={change}>
<div className="w-full h-full relative rounded-[100px]">
<div className={clsx("absolute left-0 top-0 w-[24px] h-[24px] bg-[#E9E9F1] rounded-full transition-all cursor-pointer", value === 'on' ? 'left-[100%] -translate-x-[100%]' : 'left-0')} />
</div>
</div>
)
}
}