added category infinite grid and fixed missing items at useInfiniteQuery
This commit is contained in:
parent
f591e18847
commit
4ad0357133
@ -1,302 +0,0 @@
|
||||
[
|
||||
{
|
||||
"title": "Account Executive"
|
||||
},
|
||||
{
|
||||
"title": "Engineer II"
|
||||
},
|
||||
{
|
||||
"title": "Data Coordinator"
|
||||
},
|
||||
{
|
||||
"title": "Senior Editor"
|
||||
},
|
||||
{
|
||||
"title": "Senior Quality Engineer"
|
||||
},
|
||||
{
|
||||
"title": "Statistician III"
|
||||
},
|
||||
{
|
||||
"title": "Programmer I"
|
||||
},
|
||||
{
|
||||
"title": "Office Assistant II"
|
||||
},
|
||||
{
|
||||
"title": "VP Marketing"
|
||||
},
|
||||
{
|
||||
"title": "Senior Quality Engineer"
|
||||
},
|
||||
{
|
||||
"title": "Business Systems Development Analyst"
|
||||
},
|
||||
{
|
||||
"title": "Chemical Engineer"
|
||||
},
|
||||
{
|
||||
"title": "Director of Sales"
|
||||
},
|
||||
{
|
||||
"title": "Chief Design Engineer"
|
||||
},
|
||||
{
|
||||
"title": "Editor"
|
||||
},
|
||||
{
|
||||
"title": "Speech Pathologist"
|
||||
},
|
||||
{
|
||||
"title": "Pharmacist"
|
||||
},
|
||||
{
|
||||
"title": "Operator"
|
||||
},
|
||||
{
|
||||
"title": "Human Resources Assistant III"
|
||||
},
|
||||
{
|
||||
"title": "Computer Systems Analyst II"
|
||||
},
|
||||
{
|
||||
"title": "Sales Associate"
|
||||
},
|
||||
{
|
||||
"title": "Desktop Support Technician"
|
||||
},
|
||||
{
|
||||
"title": "Executive Secretary"
|
||||
},
|
||||
{
|
||||
"title": "Quality Control Specialist"
|
||||
},
|
||||
{
|
||||
"title": "Research Associate"
|
||||
},
|
||||
{
|
||||
"title": "Software Consultant"
|
||||
},
|
||||
{
|
||||
"title": "Staff Scientist"
|
||||
},
|
||||
{
|
||||
"title": "Senior Sales Associate"
|
||||
},
|
||||
{
|
||||
"title": "Business Systems Development Analyst"
|
||||
},
|
||||
{
|
||||
"title": "VP Sales"
|
||||
},
|
||||
{
|
||||
"title": "Mechanical Systems Engineer"
|
||||
},
|
||||
{
|
||||
"title": "Information Systems Manager"
|
||||
},
|
||||
{
|
||||
"title": "Internal Auditor"
|
||||
},
|
||||
{
|
||||
"title": "Product Engineer"
|
||||
},
|
||||
{
|
||||
"title": "Legal Assistant"
|
||||
},
|
||||
{
|
||||
"title": "GIS Technical Architect"
|
||||
},
|
||||
{
|
||||
"title": "Software Consultant"
|
||||
},
|
||||
{
|
||||
"title": "Paralegal"
|
||||
},
|
||||
{
|
||||
"title": "Nurse"
|
||||
},
|
||||
{
|
||||
"title": "Biostatistician II"
|
||||
},
|
||||
{
|
||||
"title": "Web Designer I"
|
||||
},
|
||||
{
|
||||
"title": "Financial Analyst"
|
||||
},
|
||||
{
|
||||
"title": "Administrative Officer"
|
||||
},
|
||||
{
|
||||
"title": "VP Accounting"
|
||||
},
|
||||
{
|
||||
"title": "Biostatistician IV"
|
||||
},
|
||||
{
|
||||
"title": "Data Coordinator"
|
||||
},
|
||||
{
|
||||
"title": "Occupational Therapist"
|
||||
},
|
||||
{
|
||||
"title": "Web Developer IV"
|
||||
},
|
||||
{
|
||||
"title": "Quality Control Specialist"
|
||||
},
|
||||
{
|
||||
"title": "General Manager"
|
||||
},
|
||||
{
|
||||
"title": "Assistant Manager"
|
||||
},
|
||||
{
|
||||
"title": "Sales Associate"
|
||||
},
|
||||
{
|
||||
"title": "VP Marketing"
|
||||
},
|
||||
{
|
||||
"title": "Graphic Designer"
|
||||
},
|
||||
{
|
||||
"title": "Operator"
|
||||
},
|
||||
{
|
||||
"title": "Senior Financial Analyst"
|
||||
},
|
||||
{
|
||||
"title": "Information Systems Manager"
|
||||
},
|
||||
{
|
||||
"title": "Tax Accountant"
|
||||
},
|
||||
{
|
||||
"title": "Research Assistant II"
|
||||
},
|
||||
{
|
||||
"title": "Quality Engineer"
|
||||
},
|
||||
{
|
||||
"title": "Staff Scientist"
|
||||
},
|
||||
{
|
||||
"title": "Account Representative I"
|
||||
},
|
||||
{
|
||||
"title": "Clinical Specialist"
|
||||
},
|
||||
{
|
||||
"title": "Web Developer II"
|
||||
},
|
||||
{
|
||||
"title": "Desktop Support Technician"
|
||||
},
|
||||
{
|
||||
"title": "Electrical Engineer"
|
||||
},
|
||||
{
|
||||
"title": "Registered Nurse"
|
||||
},
|
||||
{
|
||||
"title": "Paralegal"
|
||||
},
|
||||
{
|
||||
"title": "Financial Advisor"
|
||||
},
|
||||
{
|
||||
"title": "Senior Cost Accountant"
|
||||
},
|
||||
{
|
||||
"title": "Senior Financial Analyst"
|
||||
},
|
||||
{
|
||||
"title": "Safety Technician III"
|
||||
},
|
||||
{
|
||||
"title": "Recruiting Manager"
|
||||
},
|
||||
{
|
||||
"title": "Engineer III"
|
||||
},
|
||||
{
|
||||
"title": "Social Worker"
|
||||
},
|
||||
{
|
||||
"title": "Assistant Manager"
|
||||
},
|
||||
{
|
||||
"title": "Financial Analyst"
|
||||
},
|
||||
{
|
||||
"title": "Health Coach II"
|
||||
},
|
||||
{
|
||||
"title": "Database Administrator III"
|
||||
},
|
||||
{
|
||||
"title": "Senior Editor"
|
||||
},
|
||||
{
|
||||
"title": "Research Nurse"
|
||||
},
|
||||
{
|
||||
"title": "Graphic Designer"
|
||||
},
|
||||
{
|
||||
"title": "Quality Engineer"
|
||||
},
|
||||
{
|
||||
"title": "Media Manager II"
|
||||
},
|
||||
{
|
||||
"title": "Payment Adjustment Coordinator"
|
||||
},
|
||||
{
|
||||
"title": "Desktop Support Technician"
|
||||
},
|
||||
{
|
||||
"title": "Legal Assistant"
|
||||
},
|
||||
{
|
||||
"title": "Research Associate"
|
||||
},
|
||||
{
|
||||
"title": "Operator"
|
||||
},
|
||||
{
|
||||
"title": "Speech Pathologist"
|
||||
},
|
||||
{
|
||||
"title": "Senior Editor"
|
||||
},
|
||||
{
|
||||
"title": "Financial Analyst"
|
||||
},
|
||||
{
|
||||
"title": "Professor"
|
||||
},
|
||||
{
|
||||
"title": "Registered Nurse"
|
||||
},
|
||||
{
|
||||
"title": "Electrical Engineer"
|
||||
},
|
||||
{
|
||||
"title": "Actuary"
|
||||
},
|
||||
{
|
||||
"title": "Nuclear Power Engineer"
|
||||
},
|
||||
{
|
||||
"title": "Social Worker"
|
||||
},
|
||||
{
|
||||
"title": "Safety Technician IV"
|
||||
},
|
||||
{
|
||||
"title": "Web Developer I"
|
||||
}
|
||||
]
|
||||
@ -1,302 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "Green Sotol"
|
||||
},
|
||||
{
|
||||
"name": "Mexican Prairie Clover"
|
||||
},
|
||||
{
|
||||
"name": "Stipulate Leaf-flower"
|
||||
},
|
||||
{
|
||||
"name": "Kawelu"
|
||||
},
|
||||
{
|
||||
"name": "Spiked Crested Coralroot"
|
||||
},
|
||||
{
|
||||
"name": "Texas Windmill Grass"
|
||||
},
|
||||
{
|
||||
"name": "Mountain Alder"
|
||||
},
|
||||
{
|
||||
"name": "Macdougal Verbena"
|
||||
},
|
||||
{
|
||||
"name": "European Aspen"
|
||||
},
|
||||
{
|
||||
"name": "Torrey's Willowherb"
|
||||
},
|
||||
{
|
||||
"name": "Ailanthus"
|
||||
},
|
||||
{
|
||||
"name": "Shortstalk Stinkweed"
|
||||
},
|
||||
{
|
||||
"name": "Black Damar"
|
||||
},
|
||||
{
|
||||
"name": "Canelillo"
|
||||
},
|
||||
{
|
||||
"name": "Veatch's Island Broom"
|
||||
},
|
||||
{
|
||||
"name": "Lewton's Milkwort"
|
||||
},
|
||||
{
|
||||
"name": "Hungarian Milkvetch"
|
||||
},
|
||||
{
|
||||
"name": "Palmer Evening Primrose"
|
||||
},
|
||||
{
|
||||
"name": "Smooth Chastetree"
|
||||
},
|
||||
{
|
||||
"name": "Jaeger's Joshua Tree"
|
||||
},
|
||||
{
|
||||
"name": "Roughhairy Maiden Fern"
|
||||
},
|
||||
{
|
||||
"name": "Florida Orchid"
|
||||
},
|
||||
{
|
||||
"name": "Belonia Lichen"
|
||||
},
|
||||
{
|
||||
"name": "Bigfruit Evening Primrose"
|
||||
},
|
||||
{
|
||||
"name": "Fitch's Tarweed"
|
||||
},
|
||||
{
|
||||
"name": "Coastal Plain Dawnflower"
|
||||
},
|
||||
{
|
||||
"name": "Clokey's Gilia"
|
||||
},
|
||||
{
|
||||
"name": "Indian Jointvetch"
|
||||
},
|
||||
{
|
||||
"name": "Wreath Lichen"
|
||||
},
|
||||
{
|
||||
"name": "Cumberland Xanthoparmelia Lichen"
|
||||
},
|
||||
{
|
||||
"name": "Spectacular Flatsedge"
|
||||
},
|
||||
{
|
||||
"name": "Pride Of California"
|
||||
},
|
||||
{
|
||||
"name": "Feverfew"
|
||||
},
|
||||
{
|
||||
"name": "Comb Wash Buckwheat"
|
||||
},
|
||||
{
|
||||
"name": "Sweet Woodreed"
|
||||
},
|
||||
{
|
||||
"name": "Delicate Violet Orchid"
|
||||
},
|
||||
{
|
||||
"name": "Canadian Blacksnakeroot"
|
||||
},
|
||||
{
|
||||
"name": "Wax Currant"
|
||||
},
|
||||
{
|
||||
"name": "Western Mountain Ash"
|
||||
},
|
||||
{
|
||||
"name": "Rhodomyrtus"
|
||||
},
|
||||
{
|
||||
"name": "Johnston's Knotweed"
|
||||
},
|
||||
{
|
||||
"name": "Kauai Bur Cucumber"
|
||||
},
|
||||
{
|
||||
"name": "Cain's Reedgrass"
|
||||
},
|
||||
{
|
||||
"name": "San Diego Pitchersage"
|
||||
},
|
||||
{
|
||||
"name": "Rock Goldenrod"
|
||||
},
|
||||
{
|
||||
"name": "Itchgrass"
|
||||
},
|
||||
{
|
||||
"name": "Threadleaf Horsebrush"
|
||||
},
|
||||
{
|
||||
"name": "Red Hills Vervain"
|
||||
},
|
||||
{
|
||||
"name": "Louisiana Bluestar"
|
||||
},
|
||||
{
|
||||
"name": "Utah Sweetvetch"
|
||||
},
|
||||
{
|
||||
"name": "Kauila"
|
||||
},
|
||||
{
|
||||
"name": "Sea Hibiscus"
|
||||
},
|
||||
{
|
||||
"name": "Derris"
|
||||
},
|
||||
{
|
||||
"name": "Florida Tasselflower"
|
||||
},
|
||||
{
|
||||
"name": "Glossy Hawthorn"
|
||||
},
|
||||
{
|
||||
"name": "Ahlner's Microcalicium Lichen"
|
||||
},
|
||||
{
|
||||
"name": "Aster"
|
||||
},
|
||||
{
|
||||
"name": "Elegant Hawthorn"
|
||||
},
|
||||
{
|
||||
"name": "Pricklypear"
|
||||
},
|
||||
{
|
||||
"name": "Parry's Sage"
|
||||
},
|
||||
{
|
||||
"name": "Redberry Buckthorn"
|
||||
},
|
||||
{
|
||||
"name": "Baden's Bluegrass"
|
||||
},
|
||||
{
|
||||
"name": "Utah Columbine"
|
||||
},
|
||||
{
|
||||
"name": "Obscure Shield Lichen"
|
||||
},
|
||||
{
|
||||
"name": "Showy Orchid"
|
||||
},
|
||||
{
|
||||
"name": "Silverleafed Princess Flower"
|
||||
},
|
||||
{
|
||||
"name": "Oahu Stenogyne"
|
||||
},
|
||||
{
|
||||
"name": "Hammond's Claytonia"
|
||||
},
|
||||
{
|
||||
"name": "Owyhee River Stickseed"
|
||||
},
|
||||
{
|
||||
"name": "Southwestern Cosmos"
|
||||
},
|
||||
{
|
||||
"name": "Toothed Flatsedge"
|
||||
},
|
||||
{
|
||||
"name": "Vegetable Fern"
|
||||
},
|
||||
{
|
||||
"name": "Rose"
|
||||
},
|
||||
{
|
||||
"name": "Desert Wishbone-bush"
|
||||
},
|
||||
{
|
||||
"name": "Rocky Mountain Woodsia"
|
||||
},
|
||||
{
|
||||
"name": "East Indian Lemongrass"
|
||||
},
|
||||
{
|
||||
"name": "Coville's Erigeron"
|
||||
},
|
||||
{
|
||||
"name": "Spiral Flag"
|
||||
},
|
||||
{
|
||||
"name": "Nevada Milkvetch"
|
||||
},
|
||||
{
|
||||
"name": "Douglas's Catchfly"
|
||||
},
|
||||
{
|
||||
"name": "Silverleaf Phacelia"
|
||||
},
|
||||
{
|
||||
"name": "Canadian Ricegrass"
|
||||
},
|
||||
{
|
||||
"name": "Barrier Range Wattle"
|
||||
},
|
||||
{
|
||||
"name": "Brooks' Alsophila"
|
||||
},
|
||||
{
|
||||
"name": "Calder's Bladderpod"
|
||||
},
|
||||
{
|
||||
"name": "Desert Brickellbush"
|
||||
},
|
||||
{
|
||||
"name": "Echeveria"
|
||||
},
|
||||
{
|
||||
"name": "Caruzo"
|
||||
},
|
||||
{
|
||||
"name": "American Black Nightshade"
|
||||
},
|
||||
{
|
||||
"name": "Whiteflower Goldenbush"
|
||||
},
|
||||
{
|
||||
"name": "Littleleaf Milkwort"
|
||||
},
|
||||
{
|
||||
"name": "Fir Mistletoe"
|
||||
},
|
||||
{
|
||||
"name": "Disc Lichen"
|
||||
},
|
||||
{
|
||||
"name": "Flagstaff Rockcress"
|
||||
},
|
||||
{
|
||||
"name": "Golden Spiderflower"
|
||||
},
|
||||
{
|
||||
"name": "Yellow Fumewort"
|
||||
},
|
||||
{
|
||||
"name": "Dot Lichen"
|
||||
},
|
||||
{
|
||||
"name": "Ross' Avens"
|
||||
},
|
||||
{
|
||||
"name": "Sierra Bluecup"
|
||||
},
|
||||
{
|
||||
"name": "Sausage Tree"
|
||||
}
|
||||
]
|
||||
@ -1,417 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "Léandre",
|
||||
"email": "amant0@hc360.com",
|
||||
"image": "https://robohash.org/autnihilquasi.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Céline",
|
||||
"email": "aedsall1@zdnet.com",
|
||||
"image": "https://robohash.org/temporibusfacerevoluptatem.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Vénus",
|
||||
"email": "bcovington2@dion.ne.jp",
|
||||
"image": "https://robohash.org/architectoconsecteturconsequatur.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Loïca",
|
||||
"email": "msteinhammer3@columbia.edu",
|
||||
"image": "https://robohash.org/pariaturullamaut.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Táng",
|
||||
"email": "fmichurin4@is.gd",
|
||||
"image": "https://robohash.org/eaquevoluptasfugit.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Andréa",
|
||||
"email": "kbygrave5@pen.io",
|
||||
"image": "https://robohash.org/eligendinatusa.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Léandre",
|
||||
"email": "bbargery6@ocn.ne.jp",
|
||||
"image": "https://robohash.org/estidquod.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Chloé",
|
||||
"email": "mharnor7@ameblo.jp",
|
||||
"image": "https://robohash.org/facilisducimuset.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Cunégonde",
|
||||
"email": "lgiovanardi8@liveinternet.ru",
|
||||
"image": "https://robohash.org/perspiciatisaliquidsit.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Dafnée",
|
||||
"email": "ebleiman9@reuters.com",
|
||||
"image": "https://robohash.org/quiquiaquaerat.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Laurène",
|
||||
"email": "ibostona@tuttocitta.it",
|
||||
"image": "https://robohash.org/quossolutaenim.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Pénélope",
|
||||
"email": "kpedrollob@nyu.edu",
|
||||
"image": "https://robohash.org/molestiasnullaomnis.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Laïla",
|
||||
"email": "noransc@symantec.com",
|
||||
"image": "https://robohash.org/rerumharumut.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Yáo",
|
||||
"email": "hsived@oaic.gov.au",
|
||||
"image": "https://robohash.org/etiuremolestias.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Judicaël",
|
||||
"email": "nfockee@wordpress.org",
|
||||
"image": "https://robohash.org/quisquamestaccusamus.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Léane",
|
||||
"email": "nalwenf@hexun.com",
|
||||
"image": "https://robohash.org/quasimagnamab.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Publicité",
|
||||
"email": "bgeorgeg@howstuffworks.com",
|
||||
"image": "https://robohash.org/repellendusquiculpa.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Kuí",
|
||||
"email": "celdonh@blinklist.com",
|
||||
"image": "https://robohash.org/doloremundequo.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Clémence",
|
||||
"email": "mcommussoi@scribd.com",
|
||||
"image": "https://robohash.org/quidoloresat.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Agnès",
|
||||
"email": "nzambonj@nymag.com",
|
||||
"image": "https://robohash.org/etnemoconsequatur.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Nadège",
|
||||
"email": "mwhithamk@bbb.org",
|
||||
"image": "https://robohash.org/accusamushicest.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Miléna",
|
||||
"email": "glisettl@merriam-webster.com",
|
||||
"image": "https://robohash.org/veniamplaceatadipisci.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Séréna",
|
||||
"email": "wpoundsm@yellowpages.com",
|
||||
"image": "https://robohash.org/nihiladea.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Chloé",
|
||||
"email": "sescalen@imgur.com",
|
||||
"image": "https://robohash.org/optiovoluptatibusaut.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Maëlyss",
|
||||
"email": "nyoskowitzo@omniture.com",
|
||||
"image": "https://robohash.org/expeditanobismaxime.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Cunégonde",
|
||||
"email": "awellenp@de.vu",
|
||||
"image": "https://robohash.org/commodiutest.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Valérie",
|
||||
"email": "zduchatelq@imageshack.us",
|
||||
"image": "https://robohash.org/omnisdoloremtemporibus.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Aloïs",
|
||||
"email": "foxburyr@github.io",
|
||||
"image": "https://robohash.org/cumnemoquia.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Eliès",
|
||||
"email": "bmactimpanys@moonfruit.com",
|
||||
"image": "https://robohash.org/eosvitaeveritatis.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Maëline",
|
||||
"email": "abaget@gnu.org",
|
||||
"image": "https://robohash.org/magnivoluptatibussoluta.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Pål",
|
||||
"email": "ssunocku@discuz.net",
|
||||
"image": "https://robohash.org/accusamusquaedelectus.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Irène",
|
||||
"email": "reneasv@sphinn.com",
|
||||
"image": "https://robohash.org/assumendahicperferendis.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Renée",
|
||||
"email": "dmclarenw@skyrock.com",
|
||||
"image": "https://robohash.org/voluptatemrecusandaeest.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Léonore",
|
||||
"email": "breimsx@tmall.com",
|
||||
"image": "https://robohash.org/natusporropariatur.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Táng",
|
||||
"email": "tcolty@usnews.com",
|
||||
"image": "https://robohash.org/autenimlabore.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Ruì",
|
||||
"email": "bblazaz@mac.com",
|
||||
"image": "https://robohash.org/quosiuremaiores.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Anaëlle",
|
||||
"email": "vsimon10@npr.org",
|
||||
"image": "https://robohash.org/velmolestiasullam.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Clémence",
|
||||
"email": "bedmonds11@economist.com",
|
||||
"image": "https://robohash.org/consequaturimpeditmollitia.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Eloïse",
|
||||
"email": "jmaster12@guardian.co.uk",
|
||||
"image": "https://robohash.org/autdolormolestiae.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Adélaïde",
|
||||
"email": "mchazier13@slate.com",
|
||||
"image": "https://robohash.org/laborereprehenderitsequi.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Séverine",
|
||||
"email": "dgisborne14@rediff.com",
|
||||
"image": "https://robohash.org/quiavoluptatemet.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Publicité",
|
||||
"email": "gjobson15@vk.com",
|
||||
"image": "https://robohash.org/eteaet.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Miléna",
|
||||
"email": "bskarin16@buzzfeed.com",
|
||||
"image": "https://robohash.org/facerenecessitatibussuscipit.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Méryl",
|
||||
"email": "jcurrington17@dropbox.com",
|
||||
"image": "https://robohash.org/molestiasasperioresmollitia.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Dù",
|
||||
"email": "aandroli18@infoseek.co.jp",
|
||||
"image": "https://robohash.org/namillumquo.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Liè",
|
||||
"email": "hcornwall19@mozilla.com",
|
||||
"image": "https://robohash.org/dignissimosconsequaturanimi.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Estève",
|
||||
"email": "mshoutt1a@amazonaws.com",
|
||||
"image": "https://robohash.org/velitsolutatotam.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Kuí",
|
||||
"email": "cdrysdall1b@51.la",
|
||||
"image": "https://robohash.org/utquodeos.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Rébecca",
|
||||
"email": "ldavall1c@vimeo.com",
|
||||
"image": "https://robohash.org/delenitiquasiid.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Eléonore",
|
||||
"email": "brickhuss1d@ustream.tv",
|
||||
"image": "https://robohash.org/consecteturcommodiiure.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Crééz",
|
||||
"email": "jaggott1e@is.gd",
|
||||
"image": "https://robohash.org/minimanisidelectus.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Célia",
|
||||
"email": "kspuffard1f@ca.gov",
|
||||
"image": "https://robohash.org/ipsumaliquidcumque.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Marie-thérèse",
|
||||
"email": "cmaharg1g@psu.edu",
|
||||
"image": "https://robohash.org/liberoquaerataut.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Publicité",
|
||||
"email": "ineeds1h@example.com",
|
||||
"image": "https://robohash.org/aliquidrepudiandaeeum.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Maïlis",
|
||||
"email": "lalliston1i@miibeian.gov.cn",
|
||||
"image": "https://robohash.org/estoptioquia.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Bérengère",
|
||||
"email": "tcullingford1j@squidoo.com",
|
||||
"image": "https://robohash.org/saepeetvel.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Styrbjörn",
|
||||
"email": "skalewe1k@archive.org",
|
||||
"image": "https://robohash.org/sednequevoluptatem.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Bérénice",
|
||||
"email": "kgoudard1l@dell.com",
|
||||
"image": "https://robohash.org/necessitatibusvelrerum.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Nuó",
|
||||
"email": "lpenticost1m@angelfire.com",
|
||||
"image": "https://robohash.org/autemsitsaepe.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Cloé",
|
||||
"email": "idomnin1n@bbc.co.uk",
|
||||
"image": "https://robohash.org/utetut.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Irène",
|
||||
"email": "ddutchburn1o@npr.org",
|
||||
"image": "https://robohash.org/etculpased.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Stéphanie",
|
||||
"email": "achafney1p@dagondesign.com",
|
||||
"image": "https://robohash.org/laudantiumilloconsequatur.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Eloïse",
|
||||
"email": "gholttom1q@salon.com",
|
||||
"image": "https://robohash.org/voluptatemestaut.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Märta",
|
||||
"email": "agierek1r@rediff.com",
|
||||
"image": "https://robohash.org/sintcommodiid.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Mélinda",
|
||||
"email": "dkeoghan1s@java.com",
|
||||
"image": "https://robohash.org/erroripsumdoloribus.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Adélie",
|
||||
"email": "bsenior1t@ca.gov",
|
||||
"image": "https://robohash.org/possimusistelabore.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Célia",
|
||||
"email": "lstenner1u@drupal.org",
|
||||
"image": "https://robohash.org/nemonihilquas.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Géraldine",
|
||||
"email": "mstephens1v@csmonitor.com",
|
||||
"image": "https://robohash.org/providentillumlibero.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Inès",
|
||||
"email": "mskacel1w@nationalgeographic.com",
|
||||
"image": "https://robohash.org/quiutvoluptatem.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Táng",
|
||||
"email": "swilber1x@reverbnation.com",
|
||||
"image": "https://robohash.org/rerumrepellatdolor.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Maëlyss",
|
||||
"email": "ncamings1y@rediff.com",
|
||||
"image": "https://robohash.org/quamminimanon.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Jú",
|
||||
"email": "jcard1z@instagram.com",
|
||||
"image": "https://robohash.org/voluptasinciduntrerum.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Lauréna",
|
||||
"email": "lstruttman20@msu.edu",
|
||||
"image": "https://robohash.org/exercitationemquiea.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Nadège",
|
||||
"email": "mcreber21@accuweather.com",
|
||||
"image": "https://robohash.org/officiisdelenitia.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Aloïs",
|
||||
"email": "lsquires22@va.gov",
|
||||
"image": "https://robohash.org/quiquasdolorum.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Loïs",
|
||||
"email": "btempest23@taobao.com",
|
||||
"image": "https://robohash.org/quisminimaaccusantium.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Åke",
|
||||
"email": "mmullan24@forbes.com",
|
||||
"image": "https://robohash.org/molestiaerepellenduseos.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Mélia",
|
||||
"email": "afarnhill25@163.com",
|
||||
"image": "https://robohash.org/quasdoloresquam.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Maëlyss",
|
||||
"email": "wpache26@sakura.ne.jp",
|
||||
"image": "https://robohash.org/doloresintrecusandae.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Loïca",
|
||||
"email": "tsoal27@taobao.com",
|
||||
"image": "https://robohash.org/accusamusilloaut.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Léana",
|
||||
"email": "gparkisson28@jiathis.com",
|
||||
"image": "https://robohash.org/omnisarchitectoaut.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Anaël",
|
||||
"email": "nslegg29@canalblog.com",
|
||||
"image": "https://robohash.org/quiaidadipisci.png?size=50x50&set=set1"
|
||||
},
|
||||
{
|
||||
"name": "Marie-josée",
|
||||
"email": "salford2a@hubpages.com",
|
||||
"image": "https://robohash.org/nihilestillo.png?size=50x50&set=set1"
|
||||
}
|
||||
]
|
||||
50
seed-data/index.ts
Normal file
50
seed-data/index.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import "dotenv/config";
|
||||
import { db } from "../src/server/db";
|
||||
import { articles, categories, users } from "../src/server/db/schema";
|
||||
|
||||
async function seed() {
|
||||
const usersData = Array.from({ length: 100 }).map((_, i) => ({
|
||||
name: `User ${i + 1}`,
|
||||
email: `user${i + 1}@example.com`,
|
||||
createdAt: new Date(Date.now() + i),
|
||||
}));
|
||||
const u = await db
|
||||
.insert(users)
|
||||
.values(usersData)
|
||||
.returning({ id: users.id });
|
||||
console.log("Seeded " + u.length + " users");
|
||||
|
||||
const categoriesData = Array.from({ length: 250 }).map((_, i) => ({
|
||||
name: `Kategorie ${i + 1}`,
|
||||
slug: `Kategorie-${i + 1}`,
|
||||
createdAt: new Date(Date.now() + i),
|
||||
}));
|
||||
const c = await db.insert(categories).values(categoriesData).returning({
|
||||
id: categories.id,
|
||||
});
|
||||
console.log("Seeded " + c.length + " categories");
|
||||
|
||||
const articlesData = Array.from({ length: 500 }).map((_, i) => ({
|
||||
title: `Artikel ${i + 1}`,
|
||||
slug: `Artikel-${i + 1}`,
|
||||
createdAt: new Date(Date.now() + i * 5),
|
||||
published: true,
|
||||
}));
|
||||
const a = await db
|
||||
.insert(articles)
|
||||
.values(articlesData)
|
||||
.returning({ id: articles.id });
|
||||
console.log("Seeded " + a.length + " articles");
|
||||
}
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
await seed();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
@ -1,42 +0,0 @@
|
||||
import "dotenv/config";
|
||||
import { db, DBType } from "../src/server/db";
|
||||
import { articles, categories, users } from "../src/server/db/schema";
|
||||
import fakeArticles from "./fake-articles.json";
|
||||
import fakeUsers from "./fake-users.json";
|
||||
import fakeCategories from "./fake-categories.json";
|
||||
import { generateSlug } from "@/lib/utils";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
|
||||
async function seed() {
|
||||
const u = await db
|
||||
.insert(users)
|
||||
.values(fakeUsers)
|
||||
.returning({ id: users.id });
|
||||
console.log("Seeded " + u.length + " users");
|
||||
|
||||
const c = await db
|
||||
.insert(categories)
|
||||
.values(
|
||||
fakeCategories.map(({ name }) => ({ name, slug: generateSlug(name) })),
|
||||
)
|
||||
.returning({
|
||||
id: categories.id,
|
||||
});
|
||||
console.log("Seeded " + c.length + " categories");
|
||||
|
||||
const a = await db
|
||||
.insert(articles)
|
||||
.values(
|
||||
fakeArticles.map(({ title }) => ({
|
||||
title,
|
||||
slug: createId(),
|
||||
published: true,
|
||||
})),
|
||||
)
|
||||
.returning({ id: articles.id });
|
||||
console.log("Seeded " + a.length + " articles");
|
||||
}
|
||||
|
||||
seed()
|
||||
.catch(console.error)
|
||||
.finally(() => process.exit());
|
||||
@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
import React from "react";
|
||||
import CategoriesGrid, {
|
||||
CategoriesGridSkeleton,
|
||||
} from "@/components/category/categories-grid";
|
||||
import CategoryGrid, {
|
||||
CategoryGridSkeleton,
|
||||
} from "@/components/category/grid/category-grid";
|
||||
import ArticleGrid, {
|
||||
ArticleGridSkeleton,
|
||||
} from "@/components/article/grid/article-grid";
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
import CategoriesGrid from "@/components/category/categories-grid";
|
||||
import InfiniteCategoryGrid from "@/components/category/grid/infinite-category-grid";
|
||||
import { api } from "@/trpc/server";
|
||||
import React from "react";
|
||||
|
||||
async function Page() {
|
||||
const categories = await api.category.getAll();
|
||||
return (
|
||||
<>
|
||||
<h1 className="text-2xl font-bold">Kategorien</h1>
|
||||
<CategoriesGrid categories={categories} />
|
||||
<InfiniteCategoryGrid />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -117,6 +117,4 @@ const sortItems: Array<{
|
||||
{ Icon: CalendarArrowDown, value: "oldest", label: "Älteste" },
|
||||
{ Icon: ArrowDownAZ, value: "abc", label: "Alphabetisch A-Z" },
|
||||
{ Icon: ArrowUpAz, value: "cba", label: "Alphabetisch Z-A" },
|
||||
// { Icon: Eye, value: "popular", label: "Beliebteste" },
|
||||
// { Icon: MessageSquare, value: "commented", label: "Meistkommentiert" },
|
||||
];
|
||||
|
||||
0
src/components/category/category-filter-bar.tsx
Normal file
0
src/components/category/category-filter-bar.tsx
Normal file
@ -1,13 +1,14 @@
|
||||
import CategoryCard from "@/components/category/category-card";
|
||||
import { Category } from "@/server/db/schema";
|
||||
import React from "react";
|
||||
import { Skeleton } from "../ui/skeleton";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
const GRID_CLASS = "grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3";
|
||||
export const CATEGORY_GRID_CLASS =
|
||||
"grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3";
|
||||
|
||||
function CategoriesGrid({ categories }: { categories: Category[] }) {
|
||||
function CategoryGrid({ categories }: { categories: Category[] }) {
|
||||
return (
|
||||
<menu className={GRID_CLASS}>
|
||||
<menu className={CATEGORY_GRID_CLASS}>
|
||||
{categories.map((category) => (
|
||||
<li key={category.id}>
|
||||
<CategoryCard {...category} />
|
||||
@ -17,12 +18,12 @@ function CategoriesGrid({ categories }: { categories: Category[] }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default CategoriesGrid;
|
||||
export default CategoryGrid;
|
||||
|
||||
export function CategoriesGridSkeleton() {
|
||||
export function CategoryGridSkeleton() {
|
||||
const range = Array.from(new Array(6).keys());
|
||||
return (
|
||||
<ul className={GRID_CLASS}>
|
||||
<ul className={CATEGORY_GRID_CLASS}>
|
||||
{range.map((i) => (
|
||||
<li key={i}>
|
||||
<Skeleton className="h-12 w-full" />
|
||||
67
src/components/category/grid/infinite-category-grid.tsx
Normal file
67
src/components/category/grid/infinite-category-grid.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
"use client";
|
||||
|
||||
import { api } from "@/trpc/react";
|
||||
import React from "react";
|
||||
|
||||
import CategoryCard from "../category-card";
|
||||
import { useInfiniteItemsObserver } from "@/lib/hooks/infinite-items-observer-hook";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { CATEGORY_GRID_CLASS } from "./category-grid";
|
||||
// import ArticleFilterBar, { ArticleFilter } from "../article-filter-bar";
|
||||
|
||||
export default function InfiniteCategoryGrid() {
|
||||
// const [filter, setFilter] = React.useState<ArticleFilter | undefined>(
|
||||
// undefined,
|
||||
// );
|
||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
|
||||
api.category.getByCursor.useInfiniteQuery(
|
||||
{
|
||||
// filter,
|
||||
},
|
||||
{
|
||||
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
||||
},
|
||||
);
|
||||
// Calculate all visible items across all loaded pages
|
||||
const allItems = React.useMemo(() => {
|
||||
return data?.pages.flatMap((page) => page.items) || [];
|
||||
}, [data]);
|
||||
|
||||
// Ref for bottom observation
|
||||
const bottomObserverRef = React.useRef(null);
|
||||
|
||||
useInfiniteItemsObserver({
|
||||
bottomObserverRef,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="relative space-y-4">
|
||||
{/* <ArticleFilterBar onFilterUpdate={setFilter} /> */}
|
||||
<menu className={`${CATEGORY_GRID_CLASS} overflow-auto`}>
|
||||
{data?.pages?.length
|
||||
? allItems.map((category, idx) => (
|
||||
<li key={`category-${idx}`}>
|
||||
<CategoryCard {...category} />
|
||||
</li>
|
||||
))
|
||||
: null}
|
||||
|
||||
{/* Loading indicator */}
|
||||
{(isLoading || isFetchingNextPage) &&
|
||||
Array.from(new Array(isLoading ? 16 : 4).keys()).map((idx) => (
|
||||
<li key={idx}>
|
||||
<Skeleton className="size-full min-h-20" />
|
||||
</li>
|
||||
))}
|
||||
|
||||
{/* Bottom observer element */}
|
||||
{hasNextPage && (
|
||||
<li ref={bottomObserverRef} className="col-span-full h-12" />
|
||||
)}
|
||||
</menu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -5,3 +5,8 @@ export const categorySchema = z.object({
|
||||
description: z.string().optional(),
|
||||
image: z.string().optional(),
|
||||
});
|
||||
|
||||
export const categoryFilterSchema = z.object({
|
||||
query: z.string().optional(),
|
||||
sort: z.string().optional(),
|
||||
});
|
||||
|
||||
@ -11,22 +11,50 @@ import {
|
||||
articleSchema,
|
||||
createArticleSchema,
|
||||
} from "@/lib/validation/zod/article";
|
||||
import { and, asc, count, desc, eq, gt, ilike, like, sql } from "drizzle-orm";
|
||||
import {
|
||||
and,
|
||||
asc,
|
||||
count,
|
||||
desc,
|
||||
eq,
|
||||
gte,
|
||||
ilike,
|
||||
like,
|
||||
lte,
|
||||
sql,
|
||||
} from "drizzle-orm";
|
||||
import { hasPermission, Role } from "@/lib/validation/permissions";
|
||||
import { generateSlug } from "@/lib/utils";
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
type ArticleCursor = Pick<Article, "slug" | "createdAt" | "title">;
|
||||
const getArticleSorting = (sort: string, cursor?: ArticleCursor) => {
|
||||
// Default to newest
|
||||
const baseCase = {
|
||||
orderBy: [desc(articles.createdAt), asc(articles.slug)],
|
||||
cursor: cursor ? lte(articles.createdAt, cursor.createdAt) : undefined,
|
||||
};
|
||||
|
||||
const getArticleSorting = (sort: string) => {
|
||||
switch (sort) {
|
||||
case "newest":
|
||||
return desc(articles.createdAt);
|
||||
return baseCase;
|
||||
case "oldest":
|
||||
return asc(articles.createdAt);
|
||||
return {
|
||||
orderBy: [asc(articles.createdAt), asc(articles.slug)],
|
||||
cursor: cursor ? gte(articles.createdAt, cursor.createdAt) : undefined,
|
||||
};
|
||||
case "abc":
|
||||
return asc(articles.title);
|
||||
return {
|
||||
orderBy: [asc(articles.title), asc(articles.slug)],
|
||||
cursor: cursor ? gte(articles.title, cursor.title) : undefined,
|
||||
};
|
||||
case "cba":
|
||||
return desc(articles.title);
|
||||
return {
|
||||
orderBy: [desc(articles.title), asc(articles.slug)],
|
||||
cursor: cursor ? lte(articles.title, cursor.title) : undefined,
|
||||
};
|
||||
default:
|
||||
return desc(articles.createdAt); // Default to newest
|
||||
return baseCase;
|
||||
}
|
||||
};
|
||||
|
||||
@ -59,15 +87,32 @@ export const articleRouter = createTRPCRouter({
|
||||
.query(async ({ ctx, input }) => {
|
||||
const { cursor } = input!;
|
||||
const limit = input?.limit ?? 50;
|
||||
const cursorArg = cursor ? gt(articles.slug, cursor) : undefined;
|
||||
|
||||
// Decode cursor if using it
|
||||
let cursorObj: ArticleCursor | undefined;
|
||||
if (cursor) {
|
||||
try {
|
||||
cursorObj = SuperJSON.parse(Buffer.from(cursor, "base64").toString());
|
||||
} catch (e) {
|
||||
// Handle invalid cursor
|
||||
cursorObj = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const queryFilterArg = input?.filter?.query?.length
|
||||
? ilike(articles.title, "%" + input.filter.query + "%")
|
||||
: undefined;
|
||||
|
||||
const categoryArg = input?.filter?.category
|
||||
? eq(articles.categoryId, input.filter.category)
|
||||
: undefined;
|
||||
const orderBy = getArticleSorting(input?.filter?.sort ?? "newest");
|
||||
|
||||
const sortConfig = input?.filter?.sort ?? "newest";
|
||||
const { orderBy, cursor: cursorArg } = getArticleSorting(
|
||||
sortConfig,
|
||||
cursorObj,
|
||||
);
|
||||
|
||||
const items = await ctx.db.query.articles.findMany({
|
||||
where: and(
|
||||
cursorArg,
|
||||
@ -83,15 +128,26 @@ export const articleRouter = createTRPCRouter({
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
let nextCursor: typeof cursor | undefined = undefined;
|
||||
|
||||
let nextCursor: string | undefined = undefined;
|
||||
if (items.length > limit) {
|
||||
const nextItem = items.pop();
|
||||
nextCursor = nextItem!.slug;
|
||||
console.log("Configure next cursor");
|
||||
const cursorItem = items.pop();
|
||||
// Create a cursor object with the relevant fields for sorting
|
||||
const cursorData: ArticleCursor = {
|
||||
slug: cursorItem!.slug,
|
||||
createdAt: cursorItem!.createdAt,
|
||||
title: cursorItem!.title,
|
||||
};
|
||||
// Encode the cursor as base64
|
||||
nextCursor = Buffer.from(SuperJSON.stringify(cursorData)).toString(
|
||||
"base64",
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
items,
|
||||
nextCursor,
|
||||
previousCursor: cursor,
|
||||
};
|
||||
}),
|
||||
getAll: publicProcedure
|
||||
@ -160,10 +216,6 @@ export const articleRouter = createTRPCRouter({
|
||||
if (!isEditor) {
|
||||
throw new Error("You are not allowed to update articles");
|
||||
}
|
||||
console.log(
|
||||
"Content before save",
|
||||
JSON.stringify(input.article.content),
|
||||
);
|
||||
|
||||
return await ctx.db
|
||||
.update(articles)
|
||||
|
||||
@ -6,10 +6,58 @@ import {
|
||||
} from "@/server/api/trpc";
|
||||
import { categories, Category } from "@/server/db/schema";
|
||||
|
||||
import { count, eq, like } from "drizzle-orm";
|
||||
import {
|
||||
and,
|
||||
asc,
|
||||
count,
|
||||
desc,
|
||||
eq,
|
||||
gt,
|
||||
gte,
|
||||
ilike,
|
||||
like,
|
||||
lte,
|
||||
} from "drizzle-orm";
|
||||
import { hasPermission, Role } from "@/lib/validation/permissions";
|
||||
import { categorySchema } from "@/lib/validation/zod/category";
|
||||
import {
|
||||
categoryFilterSchema,
|
||||
categorySchema,
|
||||
} from "@/lib/validation/zod/category";
|
||||
import { generateSlug } from "@/lib/utils";
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
type CategoryCursor = Pick<Category, "slug" | "createdAt" | "name">;
|
||||
const getCategorySorting = (sort: string, cursor?: CategoryCursor) => {
|
||||
// Default to newest
|
||||
const baseCase = {
|
||||
orderBy: [desc(categories.createdAt), asc(categories.slug)],
|
||||
cursor: cursor ? lte(categories.createdAt, cursor.createdAt) : undefined,
|
||||
};
|
||||
|
||||
switch (sort) {
|
||||
case "newest":
|
||||
return baseCase;
|
||||
case "oldest":
|
||||
return {
|
||||
orderBy: [asc(categories.createdAt), asc(categories.slug)],
|
||||
cursor: cursor
|
||||
? gte(categories.createdAt, cursor.createdAt)
|
||||
: undefined,
|
||||
};
|
||||
case "abc":
|
||||
return {
|
||||
orderBy: [asc(categories.name), asc(categories.slug)],
|
||||
cursor: cursor ? gte(categories.name, cursor.name) : undefined,
|
||||
};
|
||||
case "cba":
|
||||
return {
|
||||
orderBy: [desc(categories.name), asc(categories.slug)],
|
||||
cursor: cursor ? lte(categories.name, cursor.name) : undefined,
|
||||
};
|
||||
default:
|
||||
return baseCase;
|
||||
}
|
||||
};
|
||||
|
||||
export const categoryRouter = createTRPCRouter({
|
||||
search: publicProcedure
|
||||
@ -42,6 +90,71 @@ export const categoryRouter = createTRPCRouter({
|
||||
});
|
||||
}),
|
||||
|
||||
getByCursor: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
limit: z.number().optional(),
|
||||
cursor: z.string().optional(),
|
||||
filter: categoryFilterSchema.optional(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const { cursor } = input!;
|
||||
const limit = input?.limit ?? 50;
|
||||
|
||||
// Decode cursor if using it
|
||||
let cursorObj: CategoryCursor | undefined;
|
||||
if (cursor) {
|
||||
try {
|
||||
cursorObj = SuperJSON.parse(Buffer.from(cursor, "base64").toString());
|
||||
} catch (e) {
|
||||
// Handle invalid cursor
|
||||
cursorObj = undefined;
|
||||
}
|
||||
}
|
||||
const queryFilterArg = input?.filter?.query?.length
|
||||
? ilike(categories.name, "%" + input.filter.query + "%")
|
||||
: undefined;
|
||||
|
||||
const sortConfig = input?.filter?.sort ?? "newest";
|
||||
const { orderBy, cursor: cursorArg } = getCategorySorting(
|
||||
sortConfig,
|
||||
cursorObj,
|
||||
);
|
||||
|
||||
const items = (await ctx.db.query.categories.findMany({
|
||||
where: and(cursorArg, queryFilterArg),
|
||||
limit: limit + 1,
|
||||
orderBy,
|
||||
columns: {
|
||||
name: true,
|
||||
slug: true,
|
||||
createdAt: true,
|
||||
},
|
||||
})) as Category[];
|
||||
|
||||
let nextCursor: string | undefined = undefined;
|
||||
if (items.length > limit) {
|
||||
console.log("Configure next cursor");
|
||||
const cursorItem = items.pop();
|
||||
// Create a cursor object with the relevant fields for sorting
|
||||
const cursorData: CategoryCursor = {
|
||||
slug: cursorItem!.slug,
|
||||
createdAt: cursorItem!.createdAt,
|
||||
name: cursorItem!.name,
|
||||
};
|
||||
// Encode the cursor as base64
|
||||
nextCursor = Buffer.from(SuperJSON.stringify(cursorData)).toString(
|
||||
"base64",
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
items,
|
||||
nextCursor,
|
||||
};
|
||||
}),
|
||||
|
||||
getCount: publicProcedure.query(async ({ ctx }) => {
|
||||
return (await ctx.db.select({ count: count() }).from(categories))[0]?.count;
|
||||
}),
|
||||
|
||||
@ -29,7 +29,7 @@ export const articles = createTable(
|
||||
.primaryKey()
|
||||
.$defaultFn(() => createId())
|
||||
.notNull(),
|
||||
title: varchar("title", { length: 256 }),
|
||||
title: varchar("title", { length: 256 }).notNull(),
|
||||
slug: varchar("slug", { length: 256 }).unique().notNull(),
|
||||
authorId: varchar("author_id", { length: 255 }),
|
||||
content: jsonb("content").$type<JSONContent>(),
|
||||
@ -68,7 +68,7 @@ export const categories = createTable(
|
||||
.primaryKey()
|
||||
.$defaultFn(() => createId())
|
||||
.notNull(),
|
||||
name: varchar("name", { length: 256 }),
|
||||
name: varchar("name", { length: 256 }).notNull(),
|
||||
description: text("description"),
|
||||
slug: varchar("slug", { length: 256 }).unique().notNull(),
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user