Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"react": "^19.2.6",
"react-dom": "^19.2.6",
"react-i18next": "^17.0.8",
"sonner": "^2.0.8",
"tailwind-merge": "^3.6.0",
"three": "^0.184.0",
"tw-animate-css": "^1.3.6",
Expand Down
8 changes: 4 additions & 4 deletions src/app/components/cad/editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import { useQuery } from '@customcads/react-sdk';
import { AppError } from '@/types/errors';
import { getCadType } from '@/lib/cad';
import { useEditorStore } from '@/app/hooks/stores/useEditorStore';
import { useCadBlobUrl } from '@/app/hooks/features/cads/useCadBlobUrl';
import { useFetchCad } from '@/app/hooks/features/cads/useFetchCad';
import { useTextures } from '@/app/hooks/features/materials/useTextures';
import Loader from '@/app/components/loading';
import EditorThreeJS from './threejs';

type Props = { cadId: string };
const EditorCad = ({ cadId }: Props) => {
const { blobUrl: cadBlobUrl, progress } = useCadBlobUrl(cadId, 'Product');
const { blobUrl, progress } = useFetchCad(cadId, 'Product');
const { data: cad } = useQuery(({ cads }) => cads.single({ id: cadId }));

const materialId = useEditorStore(cadId, (state) => state.materialId);
Expand All @@ -23,7 +23,7 @@ const EditorCad = ({ cadId }: Props) => {
tip: 'Reload this page or clear your browser cache.',
});

if (!cad || !cadBlobUrl || !texture) {
if (!cad || !blobUrl || !texture) {
return <Loader progress={progress} isCad />;
}

Expand All @@ -33,7 +33,7 @@ const EditorCad = ({ cadId }: Props) => {
texture={texture}
cad={{
id: cadId,
blobUrl: cadBlobUrl,
blobUrl: blobUrl,
type: getCadType(cad.contentType as never),
coords: {
cam: cad.camCoordinates,
Expand Down
4 changes: 2 additions & 2 deletions src/app/components/cad/gallery/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { useQuery } from '@customcads/react-sdk';
import { getCadType } from '@/lib/cad';
import { useCadBlobUrl } from '@/app/hooks/features/cads/useCadBlobUrl';
import { useFetchCad } from '@/app/hooks/features/cads/useFetchCad';
import Loader from '@/app/components/loading';
import GalleryThreeJS from './threejs';

const GalleryCad = ({ cadId }: { cadId: string }) => {
const { data: cad } = useQuery(({ cads }) => cads.single({ id: cadId }));
const { blobUrl, progress } = useCadBlobUrl(cadId, 'Product');
const { blobUrl, progress } = useFetchCad(cadId, 'Product');

return (
<div className='relative h-full w-full'>
Expand Down
24 changes: 24 additions & 0 deletions src/app/components/image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { DownloadRequest, useQuery } from '@customcads/react-sdk';

type Props = {
request: DownloadRequest;
enabled?: boolean;
} & React.ComponentProps<'img'>;
const PresignedImage = ({ request, enabled, ...props }: Props) => {
const { data: image, refetch } = useQuery(({ images }) => {
const { queryKey, queryFn } = images.download(request);
return {
queryKey,
queryFn,
enabled,
staleTime: 1000 * 20,
gcTime: 1000 * 60,
};
});

return (
<img src={image?.presignedUrl} onError={() => refetch()} {...props} />
);
};

export default PresignedImage;
11 changes: 6 additions & 5 deletions src/app/components/layout/header/notifications/index.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
import { Bell } from 'lucide-react';
import { useInfiniteQuery } from '@customcads/react-sdk';
import { useNotificationRealTime } from '@/app/hooks/features/notifications/useNotificationRealTime';
import { useLiveNotifications } from '@/app/hooks/features/notifications';
import { useAuthStore } from '@/app/hooks/stores/useAuthStore';
import { popover } from '@/app/components/ui';
import CustomIcon from '@/app/components/icon';
import Scroll from './scroll';

const ALL_PARAMS = { page: 1, limit: 10 };
const bell = <CustomIcon Icon={Bell} />;

const NotificationsTab = () => {
const { is } = useAuthStore();
const query = useInfiniteQuery(
({ notifications }) => notifications.all(ALL_PARAMS),
!is.guest,
);
useNotificationRealTime({ allParams: ALL_PARAMS });
useLiveNotifications(ALL_PARAMS);

if (is.guest) return;
if (!query.data) return bell;
if (!query.data) return <CustomIcon Icon={Bell} />;
const { pages } = query.data;

return (
<popover.Root>
<popover.Trigger>{bell}</popover.Trigger>
<popover.Trigger>
<CustomIcon Icon={Bell} />
</popover.Trigger>
<popover.Content className='px-2 w-auto md:w-auto' asChild>
<div className='relative top-4 flex flex-col items-center'>
<Scroll
Expand Down
2 changes: 2 additions & 0 deletions src/app/components/layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useAuthSync } from '@/app/hooks/stores/useAuthSync';
import { useLocaleSync } from '@/app/hooks/stores/useLocaleSync';
import { useCartSync } from '@/app/hooks/stores/useCartSync';
import { useRealTime } from '@/app/hooks/hubs/useRealTime';
import { Toaster } from '../ui/sonner';
import Header from './header';
import Footer from './footer';

Expand All @@ -20,6 +21,7 @@ const Layout = ({ children }: Children) => {
<Header />
<main className='basis-full grow self-stretch flex flex-col'>
{children}
<Toaster />
</main>
</div>
<Footer />
Expand Down
10 changes: 6 additions & 4 deletions src/app/components/search/categories/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,9 @@ const Categories = ({ getCategory, updateCategory }: Props) => {
}
};

const iconClass = 'cursor-pointer w-5 h-5 md:w-5 md:h-5';
return (
<div className='flex items-center gap-x-2'>
<Funnel className={iconClass} />
<Funnel className='w-5 h-5 md:w-5 md:h-5' />
<CategoriesCombobox
current={category}
options={categories.map((x) => ({
Expand All @@ -47,12 +46,15 @@ const Categories = ({ getCategory, updateCategory }: Props) => {
}))}
onSelect={handleSelect}
>
<span className='flex justify-between items-center gap-x-2 px-8 py-3 bg-secondary text-popover-foreground text-xs md:text-lg border-2 border-border rounded-2xl cursor-pointer hover:brightness-80 transition duration-200'>
<span className='flex justify-between items-center gap-x-2 px-8 py-3 bg-secondary text-popover-foreground text-xs md:text-lg border-2 border-border rounded-2xl cursor-pointer select-none hover:brightness-80 transition duration-200'>
{category}
</span>
</CategoriesCombobox>
{category !== initial && (
<X onClick={() => handleSelect()} className={iconClass} />
<X
onClick={() => handleSelect()}
className='cursor-pointer w-5 h-5 md:w-5 md:h-5'
/>
)}
</div>
);
Expand Down
4 changes: 2 additions & 2 deletions src/app/components/search/pagination/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const Pagination = ({ total, defaultPagination, navigate }: Props) => {
return (
<pagination.Root>
<pagination.Content className='grid grid-cols-4 place-items-center gap-y-4'>
<section className='col-span-2 md:col-span-1 order-2 md:order-1'>
<section className='col-span-2 md:col-span-1 order-2 md:order-1 cursor-pointer'>
<Limits
min={1}
max={Math.ceil(total / limit)}
Expand All @@ -33,7 +33,7 @@ const Pagination = ({ total, defaultPagination, navigate }: Props) => {
onChange={handleChange.page}
/>
</section>
<section className='col-span-2 md:col-span-1 order-3 md:order-3'>
<section className='col-span-2 md:col-span-1 order-3 md:order-3 cursor-pointer'>
<Limits
min={LIMITS.GALLERY.min}
max={LIMITS.GALLERY.max}
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/search/pagination/limits.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const Limits = ({ min, max, limit, onChange }: Props) => {
return (
<pagination.Item className='flex gap-x-4'>
<select.Root onValueChange={(val) => onChange(Number(val))}>
<select.Trigger className='text-secondary-foreground text-sm md:text-lg py-1 ps-4 pe-2 rounded-lg'>
<select.Trigger className='cursor-pointer text-secondary-foreground text-sm md:text-lg py-1 ps-4 pe-2 rounded-lg select-none'>
<span className='text-xs'>{limit}</span>
</select.Trigger>
<select.Content className='max-h-100'>
Expand Down
47 changes: 25 additions & 22 deletions src/app/components/search/searchbar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,46 @@
import { useState } from 'react';
import { Search, X } from 'lucide-react';
import { Input } from '@/app/components/ui';
import { Search } from 'lucide-react';
import { Children } from '@/types/react';
import { inputGroup } from '@/app/components/ui';

type Props = {
preview?: (search: string) => Children['children'];
placeholder: string;
getSearch: () => string | undefined;
updateSearch: (searchTerm: string | undefined) => void;
};

const Searchbar = ({ placeholder, getSearch, updateSearch }: Props) => {
const Searchbar = ({
preview,
placeholder,
getSearch,
updateSearch,
}: Props) => {
const [search, setSearch] = useState(getSearch());

return (
<div className='flex items-center gap-x-3'>
{search && (
<X
onClick={() => {
setSearch(undefined);
updateSearch(undefined);
}}
className='cursor-pointer'
/>
)}
<Input
<inputGroup.Root className='relative flex items-center border-2 border-border rounded-xl min-h-11 md:min-h-14 ps-4'>
<inputGroup.Input
id='searchbar'
type='search'
placeholder={placeholder}
value={search}
onChange={({ target }) => setSearch(target.value)}
onBlur={() => updateSearch(search)}
onKeyDown={({ key }) => key === 'Enter' && updateSearch(search)}
className='bg-secondary border-2 rounded-xl min-h-11 md:min-h-14 md:min-w-75 md:px-6 text-ellipsis text-xs md:text-lg'
className='min-w-50 sm:min-w-60 md:min-w-75 lg:min-w-100 xl:min-w-120 text-ellipsis text-xs md:text-lg'
autoComplete='off'
/>
<Search
onClick={() => updateSearch(search)}
className='cursor-pointer'
/>
</div>
{preview && search && search !== getSearch() && (
<div className='left-0 top-12 md:top-15 z-10 absolute w-full'>
{preview(search)}
</div>
)}
<inputGroup.Addon className='p-0'>
<Search
onClick={() => updateSearch(search)}
className='cursor-pointer size-4 lg:size-5'
/>
</inputGroup.Addon>
</inputGroup.Root>
);
};

Expand Down
19 changes: 11 additions & 8 deletions src/app/components/search/sortings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,14 @@ const Sortings = ({ getSorting, updateSorting, sortings }: Props) => {
}
};

if (!sortings) return;

const handleSelect = (name?: string) => {
setSorting(() => name ?? initial);
updateSorting({ type: name, direction });
};
if (!sortings) return;
const format = (sorting: string) =>
sorting.replace(/([a-z])([A-Z])/g, '$1 $2');

const DirectionArrow = direction === 'ascending' ? ArrowUp : ArrowDown;

Expand All @@ -55,18 +58,18 @@ const Sortings = ({ getSorting, updateSorting, sortings }: Props) => {
<ArrowUpDown />
<SortingsCombobox
current={sorting}
options={sortings.map((x) => ({
value: x,
label: x,
}))}
options={sortings.map((x) => ({ value: x, label: format(x) }))}
onSelect={handleSelect}
>
<span className='flex justify-between items-center gap-x-2 px-8 py-3 bg-secondary text-popover-foreground text-xs md:text-lg border-2 border-border rounded-2xl cursor-pointer hover:brightness-80 transition duration-200'>
{sorting}
<span className='flex justify-between items-center gap-x-2 px-8 py-3 bg-secondary text-popover-foreground text-xs md:text-lg border-2 border-border rounded-2xl cursor-pointer select-none hover:brightness-80 transition duration-200'>
{format(sorting)}
</span>
</SortingsCombobox>
{sorting !== initial && (
<DirectionArrow onClick={toggleDirection} />
<DirectionArrow
onClick={toggleDirection}
className='cursor-pointer'
/>
)}
</div>
);
Expand Down
47 changes: 47 additions & 0 deletions src/app/components/ui/badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Slot } from 'radix-ui';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';

const badgeVariants = cva(
'group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!',
{
variants: {
variant: {
default:
'bg-primary text-primary-foreground [a]:hover:bg-primary/80',
secondary:
'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',
destructive:
'bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20',
outline:
'border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground',
ghost: 'hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50',
link: 'text-primary underline-offset-4 hover:underline',
},
},
defaultVariants: {
variant: 'default',
},
},
);

const Badge = ({
className,
variant = 'default',
asChild = false,
...props
}: React.ComponentProps<'span'> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) => {
const Comp = asChild ? Slot.Root : 'span';

return (
<Comp
data-slot='badge'
data-variant={variant}
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
);
};

export { Badge, badgeVariants };
8 changes: 4 additions & 4 deletions src/app/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ const buttonVariants = cva(
variants: {
variant: {
default:
'bg-primary text-primary-foreground hover:bg-primary/90',
'bg-primary text-primary-foreground hover:bg-primary/90 select-none',
destructive:
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 select-none',
outline:
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-border dark:hover:bg-input/50',
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-border dark:hover:bg-input/50 select-none',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
'bg-secondary text-secondary-foreground hover:bg-secondary/80 select-none',
ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
link: 'text-primary underline-offset-4 hover:underline',
},
Expand Down
Loading
Loading