Merge branch 'main' into sidebar-tray
This commit is contained in:
@@ -47,7 +47,7 @@ yarn rw dev
|
|||||||
|
|
||||||
Your browser should open automatically to `http://localhost:8910` to see the web app. Lambda functions run on `http://localhost:8911` and are also proxied to `http://localhost:8910/.redwood/functions/*`.
|
Your browser should open automatically to `http://localhost:8910` to see the web app. Lambda functions run on `http://localhost:8911` and are also proxied to `http://localhost:8910/.redwood/functions/*`.
|
||||||
|
|
||||||
If you want to access the websight on your phone use `yarn redwood dev --fwd="--host <ip-address-on-your-network-i.e.-192.168.0.5">"`
|
If you want to access the websight on your phone use `yarn redwood dev --fwd="--host <ip-address-on-your-network-i.e.-192.168.0.5>"`
|
||||||
|
|
||||||
you can sign in to the following accounts locally
|
you can sign in to the following accounts locally
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterEnum
|
||||||
|
ALTER TYPE "CadPackage" ADD VALUE 'jscad';
|
||||||
@@ -14,10 +14,6 @@ generator client {
|
|||||||
// ADMIN
|
// ADMIN
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// enum ProjectType {
|
|
||||||
// JSCAD
|
|
||||||
// }
|
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
userName String @unique // reffered to as userId in @relations
|
userName String @unique // reffered to as userId in @relations
|
||||||
@@ -41,7 +37,7 @@ model User {
|
|||||||
enum CadPackage {
|
enum CadPackage {
|
||||||
openscad
|
openscad
|
||||||
cadquery
|
cadquery
|
||||||
// jscad // TODO #422, add jscad to db schema when were ready to enable saving of jscad projects
|
jscad // TODO #422, add jscad to db schema when were ready to enable saving of jscad projects
|
||||||
}
|
}
|
||||||
|
|
||||||
model Project {
|
model Project {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export const schema = gql`
|
|||||||
enum CadPackage {
|
enum CadPackage {
|
||||||
openscad
|
openscad
|
||||||
cadquery
|
cadquery
|
||||||
|
jscad
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ module.exports = {
|
|||||||
450: '#671BC6',
|
450: '#671BC6',
|
||||||
500: '#8732F2',
|
500: '#8732F2',
|
||||||
600: '#A663FA',
|
600: '#A663FA',
|
||||||
|
200: '#C99DFF',
|
||||||
},
|
},
|
||||||
'ch-purple-gray': {
|
'ch-purple-gray': {
|
||||||
200: '#DBDBEC',
|
200: '#DBDBEC',
|
||||||
@@ -41,8 +42,11 @@ module.exports = {
|
|||||||
'ch-blue': {
|
'ch-blue': {
|
||||||
700: '#08466F',
|
700: '#08466F',
|
||||||
650: '#0958BA',
|
650: '#0958BA',
|
||||||
500: '5098F1',
|
640: '#0A57B5',
|
||||||
|
630: '#3285EB',
|
||||||
|
500: '#5098F1',
|
||||||
400: '#79B2F8',
|
400: '#79B2F8',
|
||||||
|
300: '#9BC8FF',
|
||||||
},
|
},
|
||||||
'ch-pink': {
|
'ch-pink': {
|
||||||
800: '#93064F',
|
800: '#93064F',
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"react-hotkeys-hook": "^3.4.0",
|
"react-hotkeys-hook": "^3.4.0",
|
||||||
"react-image-crop": "^8.6.6",
|
"react-image-crop": "^8.6.6",
|
||||||
|
"react-intersection-observer": "^8.32.1",
|
||||||
"react-mosaic-component": "^5.0.0",
|
"react-mosaic-component": "^5.0.0",
|
||||||
"react-tabs": "^3.2.2",
|
"react-tabs": "^3.2.2",
|
||||||
"rich-markdown-editor": "^11.0.2",
|
"rich-markdown-editor": "^11.0.2",
|
||||||
|
|||||||
13218
app/web/public/coffee-lid.stl
Normal file
13218
app/web/public/coffee-lid.stl
Normal file
File diff suppressed because it is too large
Load Diff
BIN
app/web/public/default-social-image.jpg
Normal file
BIN
app/web/public/default-social-image.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 102 KiB |
BIN
app/web/public/hinge.stl
Normal file
BIN
app/web/public/hinge.stl
Normal file
Binary file not shown.
@@ -4,6 +4,8 @@ import GoTrue from 'gotrue-js'
|
|||||||
import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web'
|
import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web'
|
||||||
import { RedwoodApolloProvider } from '@redwoodjs/web/apollo'
|
import { RedwoodApolloProvider } from '@redwoodjs/web/apollo'
|
||||||
import FatalErrorPage from 'src/pages/FatalErrorPage'
|
import FatalErrorPage from 'src/pages/FatalErrorPage'
|
||||||
|
import { createMuiTheme } from '@material-ui/core/styles'
|
||||||
|
import { ThemeProvider } from '@material-ui/styles'
|
||||||
import ReactGA from 'react-ga'
|
import ReactGA from 'react-ga'
|
||||||
|
|
||||||
ReactGA.initialize(process.env.GOOGLE_ANALYTICS_ID)
|
ReactGA.initialize(process.env.GOOGLE_ANALYTICS_ID)
|
||||||
@@ -19,12 +21,25 @@ const goTrueClient = new GoTrue({
|
|||||||
setCookie: true,
|
setCookie: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const theme = createMuiTheme({
|
||||||
|
palette: {
|
||||||
|
type: 'dark',
|
||||||
|
primary: {
|
||||||
|
light: '#C99DFF',
|
||||||
|
main: '#A663FA',
|
||||||
|
dark: '#3B0480',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<FatalErrorBoundary page={FatalErrorPage}>
|
<FatalErrorBoundary page={FatalErrorPage}>
|
||||||
<RedwoodProvider>
|
<RedwoodProvider>
|
||||||
<AuthProvider client={goTrueClient} type="goTrue">
|
<AuthProvider client={goTrueClient} type="goTrue">
|
||||||
<RedwoodApolloProvider>
|
<RedwoodApolloProvider>
|
||||||
<Routes />
|
<ThemeProvider theme={theme}>
|
||||||
|
<Routes />
|
||||||
|
</ThemeProvider>
|
||||||
</RedwoodApolloProvider>
|
</RedwoodApolloProvider>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
</RedwoodProvider>
|
</RedwoodProvider>
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ const Routes = () => {
|
|||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
|
<Route path="/projects" page={ProjectsPage} name="projects" />
|
||||||
<Route path="/dev-ide/{cadPackage}" page={DevIdePage} name="devIde" />
|
<Route path="/dev-ide/{cadPackage}" page={DevIdePage} name="devIde" />
|
||||||
<Route path="/policies/privacy-policy" page={PrivacyPolicyPage} name="privacyPolicy" />
|
<Route path="/policies/privacy-policy" page={PrivacyPolicyPage} name="privacyPolicy" />
|
||||||
<Route path="/policies/code-of-conduct" page={CodeOfConductPage} name="codeOfConduct" />
|
<Route path="/policies/code-of-conduct" page={CodeOfConductPage} name="codeOfConduct" />
|
||||||
@@ -59,7 +60,7 @@ const Routes = () => {
|
|||||||
|
|
||||||
<Private unauthenticated="home" role="admin">
|
<Private unauthenticated="home" role="admin">
|
||||||
<Route path="/admin/users" page={UsersPage} name="users" />
|
<Route path="/admin/users" page={UsersPage} name="users" />
|
||||||
<Route path="/admin/projects" page={AdminProjectsPage} name="projects" />
|
<Route path="/admin/projects" page={AdminProjectsPage} name="adminProjects" />
|
||||||
<Route path="/admin/subject-access-requests/{id}/edit" page={EditSubjectAccessRequestPage} name="editSubjectAccessRequest" />
|
<Route path="/admin/subject-access-requests/{id}/edit" page={EditSubjectAccessRequestPage} name="editSubjectAccessRequest" />
|
||||||
<Route path="/admin/subject-access-requests/{id}" page={SubjectAccessRequestPage} name="subjectAccessRequest" />
|
<Route path="/admin/subject-access-requests/{id}" page={SubjectAccessRequestPage} name="subjectAccessRequest" />
|
||||||
<Route path="/admin/subject-access-requests" page={SubjectAccessRequestsPage} name="subjectAccessRequests" />
|
<Route path="/admin/subject-access-requests" page={SubjectAccessRequestsPage} name="subjectAccessRequests" />
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
export type CadPackageType = 'openscad' | 'cadquery' | 'jscad'
|
export type CadPackageType = 'openscad' | 'cadquery' | 'jscad'
|
||||||
|
|
||||||
export const ideTypeNameMap = {
|
export const ideTypeNameMap: { [key in CadPackageType]: string } = {
|
||||||
openscad: 'OpenSCAD',
|
openscad: 'OpenSCAD',
|
||||||
cadquery: 'CadQuery',
|
cadquery: 'CadQuery',
|
||||||
|
jscad: 'JSCAD',
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CadPackageProps {
|
interface CadPackageProps {
|
||||||
@@ -19,18 +20,21 @@ const CadPackage = ({
|
|||||||
const cadName = ideTypeNameMap[cadPackage] || ''
|
const cadName = ideTypeNameMap[cadPackage] || ''
|
||||||
const isOpenScad = cadPackage === 'openscad'
|
const isOpenScad = cadPackage === 'openscad'
|
||||||
const isCadQuery = cadPackage === 'cadquery'
|
const isCadQuery = cadPackage === 'cadquery'
|
||||||
|
const isJsCad = cadPackage === 'jscad'
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
`grid grid-flow-col-dense items-center gap-2 cursor-default text-gray-100 ${
|
`grid grid-flow-col-dense items-center gap-2 text-gray-100 ${
|
||||||
isOpenScad && 'bg-yellow-800'
|
isOpenScad && 'bg-yellow-800'
|
||||||
} ${isCadQuery && 'bg-ch-blue-700'} bg-opacity-30 ` + className
|
} ${isCadQuery && 'bg-ch-blue-700'} ${
|
||||||
|
isJsCad && 'bg-ch-purple-500'
|
||||||
|
} bg-opacity-30 ` + className
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`${isOpenScad && 'bg-yellow-200'} ${
|
className={`${isOpenScad && 'bg-yellow-200'} ${
|
||||||
isCadQuery && 'bg-blue-800'
|
isCadQuery && 'bg-blue-800'
|
||||||
} ${dotClass} rounded-full`}
|
} ${isJsCad && 'bg-yellow-300'} ${dotClass} rounded-full`}
|
||||||
/>
|
/>
|
||||||
<div>{cadName}</div>
|
<div>{cadName}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
113
app/web/src/components/Hero/AssetWithGooey.tsx
Normal file
113
app/web/src/components/Hero/AssetWithGooey.tsx
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import React, { useRef, useMemo } from 'react'
|
||||||
|
import * as THREE from 'three'
|
||||||
|
import { useLoader, useThree, useFrame } from '@react-three/fiber'
|
||||||
|
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
|
||||||
|
import { useEdgeSplit } from 'src/helpers/hooks/useEdgeSplit'
|
||||||
|
import texture from 'src/components/IdeViewer/dullFrontLitMetal.png'
|
||||||
|
import { useTexture, MeshDistortMaterial, Sphere } from '@react-three/drei'
|
||||||
|
|
||||||
|
const thresholdAngle = 10
|
||||||
|
export default function AssetWithGooey({
|
||||||
|
assetUrl,
|
||||||
|
offset,
|
||||||
|
scale,
|
||||||
|
}: {
|
||||||
|
assetUrl: string
|
||||||
|
offset: number[]
|
||||||
|
scale: number
|
||||||
|
}) {
|
||||||
|
const geo = useLoader(STLLoader, assetUrl)
|
||||||
|
const edgeRef = useRef(null)
|
||||||
|
const coffeeRef = useRef(null)
|
||||||
|
const mesh = useEdgeSplit((thresholdAngle * Math.PI) / 180, true, geo)
|
||||||
|
const colorMap = useTexture(texture)
|
||||||
|
const edges = React.useMemo(() => new THREE.EdgesGeometry(geo, 12), [geo])
|
||||||
|
const position = [offset[0], offset[1], 5]
|
||||||
|
const scaleArr = Array.from({ length: 3 }).map(() => scale)
|
||||||
|
const { mouse } = useThree()
|
||||||
|
const [rEuler, rQuaternion] = useMemo(
|
||||||
|
() => [new THREE.Euler(), new THREE.Quaternion()],
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
useFrame((state, delta) => {
|
||||||
|
if (edgeRef.current) {
|
||||||
|
edgeRef.current.rotation.y += 0.01
|
||||||
|
}
|
||||||
|
if (coffeeRef.current) {
|
||||||
|
rEuler.set((-mouse.y * Math.PI) / 4, (mouse.x * Math.PI) / 2, 0)
|
||||||
|
coffeeRef.current.quaternion.slerp(rQuaternion.setFromEuler(rEuler), 0.1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<group dispose={null} ref={edgeRef} position={position}>
|
||||||
|
<group ref={coffeeRef}>
|
||||||
|
<mesh ref={mesh} scale={scaleArr} geometry={geo}>
|
||||||
|
<meshPhysicalMaterial
|
||||||
|
envMapIntensity={2}
|
||||||
|
color="#F472B6"
|
||||||
|
map={colorMap}
|
||||||
|
clearcoat={0.5}
|
||||||
|
clearcoatRoughness={0.01}
|
||||||
|
roughness={0}
|
||||||
|
metalness={0.7}
|
||||||
|
smoothShading
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
<lineSegments scale={scale} geometry={edges} renderOrder={100}>
|
||||||
|
<lineBasicMaterial color="#aaaaff" />
|
||||||
|
</lineSegments>
|
||||||
|
</group>
|
||||||
|
<ambientLight intensity={2} />
|
||||||
|
<Gooey />
|
||||||
|
</group>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomSign(num: number): number {
|
||||||
|
return Math.random() > 0.5 ? num : -num
|
||||||
|
}
|
||||||
|
|
||||||
|
function Gooey() {
|
||||||
|
const blobsData = useMemo(() => {
|
||||||
|
const firstSet = Array.from({ length: 5 }).map((_, index) => {
|
||||||
|
const dist = Math.random() * 3 + 2.5
|
||||||
|
const x = randomSign(Math.random() * dist)
|
||||||
|
const y = randomSign(Math.sqrt(dist * dist - x * x))
|
||||||
|
const z = randomSign(Math.random() * 3)
|
||||||
|
const position: [number, number, number] = [x, z, y]
|
||||||
|
const size = Math.random() * 0.8 + 0.1
|
||||||
|
const distort = Math.random() * 0.8 + 0.1
|
||||||
|
const speed = (Math.random() * 0.8) / size / size + 0.1
|
||||||
|
return { position, size, distort, speed }
|
||||||
|
})
|
||||||
|
const secondSet = Array.from({ length: 5 }).map((_, index) => {
|
||||||
|
const dist = Math.random() * 3 + 1.5
|
||||||
|
const x = randomSign(Math.random() * dist)
|
||||||
|
const y = randomSign(Math.sqrt(dist * dist - x * x))
|
||||||
|
const z = randomSign(Math.random() * 3)
|
||||||
|
const position: [number, number, number] = [x, z, y]
|
||||||
|
const size = Math.random() * 0.2 + 0.05
|
||||||
|
const distort = Math.random() * 0.8 + 0.1
|
||||||
|
const speed = (Math.random() * 0.5) / size / size + 0.1
|
||||||
|
return { position, size, distort, speed }
|
||||||
|
})
|
||||||
|
return [...firstSet, ...secondSet]
|
||||||
|
}, [])
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{blobsData.map(({ position, size, distort, speed }, index) => (
|
||||||
|
<Sphere key={index} visible position={position} args={[size, 16, 200]}>
|
||||||
|
<MeshDistortMaterial
|
||||||
|
color="#173E6F"
|
||||||
|
attach="material"
|
||||||
|
distort={distort} // Strength, 0 disables the effect (default=1)
|
||||||
|
speed={speed} // Speed (default=1)
|
||||||
|
roughness={0.2}
|
||||||
|
opacity={0.6}
|
||||||
|
transparent
|
||||||
|
/>
|
||||||
|
</Sphere>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
504
app/web/src/components/Hero/Hero.tsx
Normal file
504
app/web/src/components/Hero/Hero.tsx
Normal file
@@ -0,0 +1,504 @@
|
|||||||
|
import { Canvas, useLoader, useFrame } from '@react-three/fiber'
|
||||||
|
import { Suspense } from 'react'
|
||||||
|
import { Html, Stats } from '@react-three/drei'
|
||||||
|
import CadPackage, {
|
||||||
|
CadPackageType,
|
||||||
|
} from 'src/components/CadPackage/CadPackage'
|
||||||
|
import { navigate, routes, Link } from '@redwoodjs/router'
|
||||||
|
import { useInView } from 'react-intersection-observer'
|
||||||
|
|
||||||
|
import Svg, { SvgNames } from 'src/components/Svg/Svg'
|
||||||
|
import Gravatar from 'src/components/Gravatar/Gravatar'
|
||||||
|
import ProjectsCell from 'src/components/ProjectsCell'
|
||||||
|
import OutBound from 'src/components/OutBound/OutBound'
|
||||||
|
|
||||||
|
// dynamic import to enable pre-render iof the homepage
|
||||||
|
const Coffee = React.lazy(() => import('src/components/Hero/AssetWithGooey'))
|
||||||
|
|
||||||
|
export const Hero = () => {
|
||||||
|
const [width, widthSetter] = React.useState(1024)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const onResize = () => {
|
||||||
|
widthSetter(window.innerWidth)
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', onResize)
|
||||||
|
onResize()
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', onResize)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
const { heroOffset, tutOffset } = React.useMemo(() => {
|
||||||
|
if (width < 1024) {
|
||||||
|
return {
|
||||||
|
heroOffset: [0, -3],
|
||||||
|
tutOffset: [0, -3],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
heroOffset: [-5, 0],
|
||||||
|
tutOffset: [4, 0],
|
||||||
|
}
|
||||||
|
}, [width])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-ch-gray-800">
|
||||||
|
<ModelSection assetUrl="/coffee-lid.stl" offset={heroOffset} scale={0.06}>
|
||||||
|
<div className="grid lg:grid-cols-2 py-32">
|
||||||
|
<div className="flex items-end justify-center row-start-2 lg:row-start-1 pt-96 lg:pt-0 pr-12 pl-6">
|
||||||
|
<Link
|
||||||
|
to={routes.project({
|
||||||
|
userName: 'irevdev',
|
||||||
|
projectTitle: 'coffee-lid',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className="grid grid-flow-col gap-2 sm:gap-4 items-center bg-ch-gray-760 bg-opacity-95 text-ch-gray-300 rounded-md p-2 font-fira-sans relative z-10 shadow-ch">
|
||||||
|
<div className="pl-1 sm:pl-4">
|
||||||
|
<Gravatar
|
||||||
|
image="CadHub/xvrnxvarkv8tdzo4n65u"
|
||||||
|
className="w-12 h-12 mr-4"
|
||||||
|
size={60}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-xl sm:text-3xl">Coffee Lid</div>
|
||||||
|
<div>IrevDev</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex self-start">
|
||||||
|
<CadPackage
|
||||||
|
cadPackage="cadquery"
|
||||||
|
className="px-3 py-1 sm:text-xl rounded transform translate-x-4 sm:translate-x-10"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-start-1 lg:col-start-2 px-4">
|
||||||
|
<div>
|
||||||
|
<span
|
||||||
|
className="text-7xl text-ch-blue-400 bg-ch-blue-640 bg-opacity-30 font-fira-code px-6 rounded-2xl shadow-ch"
|
||||||
|
style={{
|
||||||
|
boxShadow: 'inset 0 4px 4px 0 rgba(255,255,255, 0.06)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Code
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-6xl font-fira-sans mt-8 text-ch-gray-300">
|
||||||
|
is the future of CAD
|
||||||
|
</div>
|
||||||
|
<div className="text-2xl text-gray-600 mt-8 max-w-4xl">
|
||||||
|
Designs backed by reliable, easy-to-write code open a world of new
|
||||||
|
workflows and collaboration. We're building a place where you can
|
||||||
|
build that future.
|
||||||
|
</div>
|
||||||
|
<OutlineButton
|
||||||
|
color="pink"
|
||||||
|
isLeft
|
||||||
|
svgName="terminal"
|
||||||
|
onClick={() =>
|
||||||
|
navigate(routes.draftProject({ cadPackage: 'openscad' }))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Start Hacking
|
||||||
|
</OutlineButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModelSection>
|
||||||
|
<ChooseYourCharacter />
|
||||||
|
<Community />
|
||||||
|
<ModelSection assetUrl="/hinge.stl" offset={tutOffset} scale={0.12}>
|
||||||
|
<div className="max-w-7xl mx-auto grid py-16 overflow-hidden">
|
||||||
|
<div className="py-0 pb-32 lg:py-32 ml-4 col-start-1 lg:col-start-1 pr-12 pl-6">
|
||||||
|
<div className="text-4xl mb-6 text-ch-gray-300">Learn Code-CAD</div>
|
||||||
|
|
||||||
|
<p className="text-gray-600 max-w-lg">
|
||||||
|
We want you to learn Code-CAD today so it can change the way you
|
||||||
|
work tomorrow. Our community is writing tutorials to make this
|
||||||
|
powerful paradigm more accessible to people new to code and CAD.
|
||||||
|
</p>
|
||||||
|
<OutBound
|
||||||
|
to="https://learn.cadhub.xyz/docs/definitive-beginners/your-openscad-journey"
|
||||||
|
className=""
|
||||||
|
>
|
||||||
|
<OutlineButton color="pink" isLeft svgName="terminal">
|
||||||
|
Get Started with OpenSCAD
|
||||||
|
</OutlineButton>
|
||||||
|
</OutBound>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-end justify-center row-start-2 lg:row-start-1 lg:col-start-2 pt-96 lg:pt-0 lg:pr-10">
|
||||||
|
<Link
|
||||||
|
to={routes.project({
|
||||||
|
userName: 'irevdev',
|
||||||
|
projectTitle: 'tutorial-hinge',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className="grid grid-flow-col sm:gap-2 items-center bg-ch-gray-760 bg-opacity-95 text-ch-gray-300 rounded-md py-2 pl-2 font-fira-sans relative z-10 shadow-ch">
|
||||||
|
<div className="pl-1 sm:pl-4">
|
||||||
|
<Gravatar
|
||||||
|
image="CadHub/xvrnxvarkv8tdzo4n65u"
|
||||||
|
className="w-12 h-12 mr-4"
|
||||||
|
size={60}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-lg sm:text-2xl w-28 sm:w-auto">
|
||||||
|
Print in Place Hinge
|
||||||
|
</div>
|
||||||
|
<div>IrevDev</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex self-start">
|
||||||
|
<CadPackage
|
||||||
|
cadPackage="openscad"
|
||||||
|
className="px-3 py-1 sm:text-xl rounded transform translate-x-4 sm:translate-x-10"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModelSection>
|
||||||
|
<Roadmap />
|
||||||
|
<div className="h-3 bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500" />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const DisableRender = () => useFrame(() => null, 1000)
|
||||||
|
|
||||||
|
function ModelSection({
|
||||||
|
assetUrl,
|
||||||
|
offset,
|
||||||
|
children,
|
||||||
|
scale,
|
||||||
|
}: {
|
||||||
|
assetUrl: string
|
||||||
|
offset: number[]
|
||||||
|
children: React.ReactNode
|
||||||
|
scale: number
|
||||||
|
}) {
|
||||||
|
const { ref, inView } = useInView()
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
{children}
|
||||||
|
<div className="absolute inset-0" ref={ref}>
|
||||||
|
<Canvas
|
||||||
|
linear
|
||||||
|
dpr={[1, 2]}
|
||||||
|
orthographic
|
||||||
|
camera={{ zoom: 75, position: [0, 0, 500] }}
|
||||||
|
>
|
||||||
|
{!inView && <DisableRender />}
|
||||||
|
<pointLight position={[2, 3, 5]} color="#FFFFFF" intensity={2} />
|
||||||
|
<pointLight position={[2, 3, -5]} color="#FFFFFF" intensity={2} />
|
||||||
|
<pointLight position={[-6, 3, -5]} color="#FFFFFF" intensity={2} />
|
||||||
|
<pointLight position={[-6, 3, 5]} color="#FFFFFF" intensity={2} />
|
||||||
|
|
||||||
|
<pointLight position={[2, 1.5, 0]} color="#0000FF" intensity={2} />
|
||||||
|
<pointLight position={[2, 1.5, 0]} color="#FF0000" intensity={2} />
|
||||||
|
<Suspense
|
||||||
|
fallback={<Html center className="loading" children="Loading..." />}
|
||||||
|
>
|
||||||
|
<Coffee assetUrl={assetUrl} offset={offset} scale={scale} />
|
||||||
|
</Suspense>
|
||||||
|
|
||||||
|
{/* uncomment for framerate and render time */}
|
||||||
|
{/* <Stats showPanel={0} className="three-debug-panel-1" /> */}
|
||||||
|
{/* <Stats showPanel={1} className="three-debug-panel-2" /> */}
|
||||||
|
</Canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChooseYourCharacter() {
|
||||||
|
return (
|
||||||
|
<div className="text-ch-gray-300 grid lg:grid-cols-2 gap-12 font-fira-sans py-32 max-w-7xl mx-auto px-4">
|
||||||
|
<div className="">
|
||||||
|
<div className="text-4xl mb-6">Choose your character</div>
|
||||||
|
<p className="text-gray-600 text-2xl">
|
||||||
|
CadHub is the place you can try out Code-CAD packages to find the one
|
||||||
|
that's right for you. Our dedicated community is making CAD easy to
|
||||||
|
learn on the web. Try one of our three integrations today and keep an
|
||||||
|
eye out for more.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ul className="flex-col flex justify-around items-center lg:items-start text-gray-600">
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
cadPackage: 'openscad',
|
||||||
|
desc: 'A mature Code-CAD library focused on Constructed Solid Geometry (CSG) modeling with syntax like C++.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cadPackage: 'cadquery',
|
||||||
|
desc: 'A Python-based library with support for CSG and sketch-based modeling and a clean-feeling API.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cadPackage: 'jscad',
|
||||||
|
desc: 'A JavaScript Code-CAD library that will feel familiar to web developers, based on the same tech as OpenSCAD.',
|
||||||
|
},
|
||||||
|
].map(
|
||||||
|
({
|
||||||
|
cadPackage,
|
||||||
|
desc,
|
||||||
|
}: {
|
||||||
|
cadPackage: CadPackageType
|
||||||
|
desc: string
|
||||||
|
}) => (
|
||||||
|
<li key={cadPackage} className="flex items-center">
|
||||||
|
<div className="mr-4 sm:mr-12">
|
||||||
|
<button
|
||||||
|
onClick={() => navigate(routes.draftProject({ cadPackage }))}
|
||||||
|
className="flex-shrink-0 cursor-pointer"
|
||||||
|
>
|
||||||
|
<CadPackage
|
||||||
|
cadPackage={cadPackage}
|
||||||
|
className="px-3 py-1 w-40 text-xl rounded"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm my-2 max-w-sm">{desc}</p>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Community() {
|
||||||
|
return (
|
||||||
|
<div className="max-w-7xl mx-auto py-40">
|
||||||
|
<div className="text-ch-gray-300 grid lg:grid-cols-2 gap-8 font-fira-sans px-4 mb-6">
|
||||||
|
<div className="text-4xl">Explore with our community</div>
|
||||||
|
|
||||||
|
<p className="text-gray-600 text-sm">
|
||||||
|
CadHub is a social platform. You can ask users how they designed a
|
||||||
|
part, fork their work to put your own spin on it, and find inspiration
|
||||||
|
in abundance.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ProjectsCell shouldFilterProjectsWithoutImage projectLimit={8} />
|
||||||
|
<div className="flex justify-end pr-4">
|
||||||
|
<OutlineButton
|
||||||
|
color="blue"
|
||||||
|
svgName="arrow-right"
|
||||||
|
onClick={() => navigate(routes.projects())}
|
||||||
|
>
|
||||||
|
See All Projects
|
||||||
|
</OutlineButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function OutlineButton({
|
||||||
|
color,
|
||||||
|
svgName,
|
||||||
|
isLeft = false,
|
||||||
|
children,
|
||||||
|
onClick,
|
||||||
|
}: {
|
||||||
|
color: 'blue' | 'pink' | 'purple'
|
||||||
|
svgName: SvgNames
|
||||||
|
isLeft?: boolean
|
||||||
|
children: React.ReactNode
|
||||||
|
onClick?: () => void
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={onClick}
|
||||||
|
className={`grid grid-flow-col-dense gap-4 items-center border px-4 py-1 rounded mt-6 relative z-10 ${
|
||||||
|
color === 'pink' && 'border-ch-pink-500'
|
||||||
|
} ${color === 'blue' && 'border-ch-blue-630'} ${
|
||||||
|
color === 'purple' && 'border-ch-purple-500'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{isLeft && (
|
||||||
|
<Svg
|
||||||
|
name={svgName}
|
||||||
|
className={`${color === 'pink' && 'text-ch-pink-500'} ${
|
||||||
|
color === 'blue' && 'text-ch-blue-300'
|
||||||
|
} ${color === 'purple' && 'text-ch-purple-200'} w-6 h-6`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span
|
||||||
|
className={`text-2xl ${color === 'pink' && 'text-ch-pink-300'} ${
|
||||||
|
color === 'blue' && 'text-ch-blue-300'
|
||||||
|
} ${color === 'purple' && 'text-ch-purple-200'}`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
{!isLeft && (
|
||||||
|
<Svg
|
||||||
|
name={svgName}
|
||||||
|
className={`${color === 'pink' && 'text-ch-pink-500'} ${
|
||||||
|
color === 'blue' && 'text-ch-blue-300'
|
||||||
|
} ${color === 'purple' && 'text-ch-purple-200'} w-6 h-6`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Roadmap() {
|
||||||
|
const sections = [
|
||||||
|
{
|
||||||
|
title: 'Read our roadmap',
|
||||||
|
desc: 'Version control with GitHub, multi-file projects, and team collaboration tools. We’ve got a lot planned, and we’re building it in the open.',
|
||||||
|
buttonText: 'View on Github',
|
||||||
|
color: 'purple',
|
||||||
|
url: 'https://github.com/Irev-Dev/cadhub/discussions/212',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Join our community',
|
||||||
|
desc: 'CAD is ready to evolve. Join our Discord and opensource community on GitHub and build that future with us!',
|
||||||
|
buttonText: 'Join the Discord',
|
||||||
|
color: 'blue',
|
||||||
|
url: 'https://discord.gg/SD7zFRNjGH',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
return (
|
||||||
|
<div className="max-w-7xl mx-auto grid md:grid-cols-2 py-32 mt-12">
|
||||||
|
{sections.map(({ title, desc, buttonText, color, url }) => (
|
||||||
|
<div className="ml-4 py-6" key={title}>
|
||||||
|
<div className="text-4xl mb-6 text-ch-gray-300">{title}</div>
|
||||||
|
<p className="text-gray-600 text-2xl max-w-lg">{desc}</p>
|
||||||
|
<OutBound to={url} className="">
|
||||||
|
<OutlineButton color={color} svgName="arrow-right">
|
||||||
|
{buttonText}
|
||||||
|
</OutlineButton>
|
||||||
|
</OutBound>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Footer() {
|
||||||
|
const section: {
|
||||||
|
header: string
|
||||||
|
links: { name: string; url: string }[]
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
header: 'Community',
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
name: 'Github',
|
||||||
|
url: 'https://github.com/Irev-Dev/cadhub',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Discord',
|
||||||
|
url: 'https://discord.gg/SD7zFRNjGH',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Newsletter',
|
||||||
|
url: 'https://kurthutten.com/signup/',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'About',
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
name: 'Road Map',
|
||||||
|
url: 'https://github.com/Irev-Dev/cadhub/discussions/212',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Code of Conduct',
|
||||||
|
url: '/policies/code-of-conduct',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Privacy Policy',
|
||||||
|
url: '/policies/privacy-policy',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Learn',
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
name: 'Documentation',
|
||||||
|
url: 'https://learn.cadhub.xyz/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Blog',
|
||||||
|
url: 'https://learn.cadhub.xyz/blog',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Integrations',
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
name: 'OpenSCAD',
|
||||||
|
url: 'https://openscad.org/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'CadQuery',
|
||||||
|
url: 'https://cadquery.readthedocs.io/en/latest/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'JSCAD',
|
||||||
|
url: 'https://github.com/jscad',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
return (
|
||||||
|
<div className="max-w-7xl mx-auto py-16 px-4 grid">
|
||||||
|
<div className="pl-20 lg:pl-0">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="rounded-full overflow-hidden">
|
||||||
|
<Svg className="w-10 md:w-16" name="favicon" />
|
||||||
|
</div>
|
||||||
|
<div className="ml-2 md:ml-8 flex">
|
||||||
|
{/* Because of how specific these styles are to this heading/logo and it doesn't need to be replicated else where as well as it's very precise with the placement of "pre-alpha" I think it's appropriate. */}
|
||||||
|
<h2
|
||||||
|
className="text-indigo-300 text-2xl md:text-5xl font-ropa-sans py-1 md:tracking-wider"
|
||||||
|
style={{ letterSpacing: '0.3em' }}
|
||||||
|
>
|
||||||
|
CadHub
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
className="text-pink-400 text-sm font-bold font-ropa-sans hidden md:block"
|
||||||
|
style={{ paddingBottom: '2rem', marginLeft: '-1.8rem' }}
|
||||||
|
>
|
||||||
|
pre-alpha
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-600 text-xl mt-12 max-w-xs">
|
||||||
|
Built by{' '}
|
||||||
|
<OutBound
|
||||||
|
to="https://github.com/Irev-Dev/cadhub/graphs/contributors"
|
||||||
|
className="font-bold"
|
||||||
|
>
|
||||||
|
16 contributors
|
||||||
|
</OutBound>{' '}
|
||||||
|
from around the world.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid sm:grid-cols-4 gap-4 flex-grow pl-20 row-start-2 lg:col-start-2 lg:row-start-1 mt-20 lg:mt-0">
|
||||||
|
{section.map(({ header, links }) => (
|
||||||
|
<ul
|
||||||
|
className="text-ch-gray-300 font-fira-sans pt-8 sm:pt-0"
|
||||||
|
key={header}
|
||||||
|
>
|
||||||
|
<li className="text-xl font-bold">{header}</li>
|
||||||
|
{links.map(({ name, url }) => (
|
||||||
|
<li className="text-lg mt-6 font-light" key={url}>
|
||||||
|
<a href={url}>{name}</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -8,20 +8,15 @@ const InputText = ({ type = 'text', className, name, validation }) => {
|
|||||||
} = useFormContext()
|
} = useFormContext()
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={getActiveClasses('relative inline-block', className)}>
|
<div className={getActiveClasses('relative mt-5', className)}>
|
||||||
<FieldError
|
<FieldError
|
||||||
className="absolute -my-4 text-sm text-red-500 font-ropa-sans"
|
className="absolute -my-5 text-sm text-red-500 font-ropa-sans"
|
||||||
name={name}
|
name={name}
|
||||||
/>
|
/>
|
||||||
<div
|
|
||||||
className={getActiveClasses(
|
|
||||||
'absolute inset-0 mb-2 rounded bg-gray-200 shadow-inner',
|
|
||||||
{ 'border border-red-500': errors[name] }
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<TextField
|
<TextField
|
||||||
className={getActiveClasses(
|
className={getActiveClasses(
|
||||||
'pl-2 pt-1 text-indigo-800 font-medium mb-px pb-px bg-transparent relative w-full'
|
'text-ch-gray-300 rounded-none bg-ch-gray-600 border border-transparent focus:border-ch-gray-300 px-2 py-1 relative w-full',
|
||||||
|
{ 'border border-red-600': errors[name] }
|
||||||
)}
|
)}
|
||||||
name={name}
|
name={name}
|
||||||
readOnly={false}
|
readOnly={false}
|
||||||
|
|||||||
@@ -55,8 +55,17 @@ const LoginModal = ({ open, onClose, shouldStartWithSignup = false }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={onClose} className={classes.root}>
|
<Dialog
|
||||||
<div className="bg-gray-100 max-w-2xl rounded-lg shadow-lg">
|
open={open}
|
||||||
|
onClose={onClose}
|
||||||
|
className={classes.root}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="bg-ch-gray-700 max-w-2xl rounded-lg shadow-lg text-ch-gray-300">
|
||||||
<Tabs
|
<Tabs
|
||||||
value={tab}
|
value={tab}
|
||||||
onChange={onTabChange}
|
onChange={onTabChange}
|
||||||
@@ -89,8 +98,8 @@ const LoginModal = ({ open, onClose, shouldStartWithSignup = false }) => {
|
|||||||
|
|
||||||
const Field = ({ name, type = 'text', validation }) => (
|
const Field = ({ name, type = 'text', validation }) => (
|
||||||
<>
|
<>
|
||||||
<span className="capitalize text-gray-500 text-sm align-middle my-3">
|
<span className="capitalize text-ch-gray-300 text-right text-sm align-middle my-3">
|
||||||
{name}:
|
{name}
|
||||||
</span>
|
</span>
|
||||||
<InputTextForm
|
<InputTextForm
|
||||||
type={type}
|
type={type}
|
||||||
@@ -102,8 +111,8 @@ const Field = ({ name, type = 'text', validation }) => (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const HeroButton = ({ text }) => (
|
const HeroButton = ({ text }) => (
|
||||||
<Submit className="bg-texture bg-purple-800 py-6 w-full flex items-center justify-center rounded-b border border-indigo-300 border-opacity-0 hover:border-opacity-100 hover:shadow-xl">
|
<Submit className="bg-texture bg-ch-purple-400 bg-opacity-50 py-6 w-full flex items-center justify-center rounded-b border border-indigo-300 border-opacity-0 hover:bg-opacity-80 hover:shadow-xl">
|
||||||
<span className="font-bold text-2xl text-indigo-200">{text}</span>
|
<span className="text-3xl text-ch-purple-600">{text}</span>
|
||||||
</Submit>
|
</Submit>
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -129,13 +138,14 @@ const SignInForm = ({ onSubmitSignIn }) => (
|
|||||||
type="password"
|
type="password"
|
||||||
validation={{ required: true }}
|
validation={{ required: true }}
|
||||||
/>
|
/>
|
||||||
|
<div></div>
|
||||||
|
<Link
|
||||||
|
to={routes.accountRecovery()}
|
||||||
|
className="underline text-sm text-ch-gray-400 block mt-4"
|
||||||
|
>
|
||||||
|
forgot your password?
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<Link
|
|
||||||
to={routes.accountRecovery()}
|
|
||||||
className="underline text-sm text-gray-500 block text-center"
|
|
||||||
>
|
|
||||||
forgot your password?
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
<HeroButton text="Sign In" />
|
<HeroButton text="Sign In" />
|
||||||
</Form>
|
</Form>
|
||||||
@@ -174,22 +184,25 @@ const SignUpForm = ({ onSubmitSignUp, checkBox, setCheckBox, onClose }) => (
|
|||||||
type="password"
|
type="password"
|
||||||
validation={{ required: true }}
|
validation={{ required: true }}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div className="flex pt-4">
|
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
id="signup-toc"
|
||||||
|
className="justify-self-end mr-2"
|
||||||
checked={checkBox}
|
checked={checkBox}
|
||||||
onChange={() => setCheckBox(!checkBox)}
|
onChange={() => setCheckBox(!checkBox)}
|
||||||
/>{' '}
|
/>{' '}
|
||||||
<span className="pl-4 text-gray-500 text-sm max-w-sm">
|
<label
|
||||||
|
htmlFor="signup-toc"
|
||||||
|
className="text-ch-gray-400 text-sm mt-4 cursor-pointer"
|
||||||
|
>
|
||||||
Stay up-to-date with CadHub's progress with the founder's (
|
Stay up-to-date with CadHub's progress with the founder's (
|
||||||
<OutBound className="underline" to="https://twitter.com/IrevDev">
|
<OutBound className="underline" to="https://twitter.com/IrevDev">
|
||||||
Kurt's
|
Kurt's
|
||||||
</OutBound>
|
</OutBound>
|
||||||
) newsletter
|
) newsletter
|
||||||
</span>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-gray-500 block text-center pt-4">
|
<span className="text-sm text-ch-gray-400 block text-center pt-4">
|
||||||
Use of CadHub requires you to abide by our{' '}
|
Use of CadHub requires you to abide by our{' '}
|
||||||
<Link
|
<Link
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
|
|||||||
@@ -1,39 +1,60 @@
|
|||||||
import { Link, routes } from '@redwoodjs/router'
|
import { Link, routes } from '@redwoodjs/router'
|
||||||
import Svg from 'src/components/Svg/Svg'
|
import Svg from 'src/components/Svg/Svg'
|
||||||
import { Popover } from '@headlessui/react'
|
import { Popover } from '@headlessui/react'
|
||||||
import type { CadPackageType } from 'src/components/CadPackage/CadPackage'
|
import { CadPackageType } from 'src/components/CadPackage/CadPackage'
|
||||||
|
|
||||||
const menuOptions: {
|
const menuOptions: {
|
||||||
name: string
|
name: string
|
||||||
sub: string
|
sub: string
|
||||||
|
dotClasses: string
|
||||||
|
bgClasses: string
|
||||||
ideType: CadPackageType
|
ideType: CadPackageType
|
||||||
}[] = [
|
}[] = [
|
||||||
{
|
{
|
||||||
name: 'OpenSCAD',
|
name: 'OpenSCAD',
|
||||||
sub: 'beta',
|
sub: 'beta',
|
||||||
|
bgClasses: 'bg-yellow-800',
|
||||||
|
dotClasses: 'bg-yellow-200',
|
||||||
ideType: 'openscad',
|
ideType: 'openscad',
|
||||||
},
|
},
|
||||||
{ name: 'CadQuery', sub: 'beta', ideType: 'cadquery' },
|
{
|
||||||
// { name: 'JSCAD', sub: 'alpha', ideType: 'jscad' }, // TODO #422, add jscad to db schema when were ready to enable saving of jscad projects
|
name: 'CadQuery',
|
||||||
|
sub: 'beta',
|
||||||
|
bgClasses: 'bg-ch-blue-700',
|
||||||
|
dotClasses: 'bg-blue-800',
|
||||||
|
ideType: 'cadquery',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'JSCAD',
|
||||||
|
sub: 'beta',
|
||||||
|
bgClasses: 'bg-ch-purple-500',
|
||||||
|
dotClasses: 'bg-yellow-300',
|
||||||
|
ideType: 'jscad',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const NavPlusButton: React.FC = () => {
|
const NavPlusButton: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<Popover className="relative outline-none w-full h-full">
|
<Popover className="relative outline-none w-full h-full">
|
||||||
<Popover.Button className="h-full w-full outline-none hover:bg-ch-gray-550 border rounded-full">
|
<Popover.Button className="h-full w-full outline-none hover:bg-ch-gray-550 border-ch-gray-400 border-2 rounded-full">
|
||||||
<Svg name="plus" className="text-gray-200" />
|
<Svg name="plus" className="text-ch-gray-300" />
|
||||||
</Popover.Button>
|
</Popover.Button>
|
||||||
|
|
||||||
<Popover.Panel className="absolute z-10 right-0">
|
<Popover.Panel className="absolute z-10 right-0 bg-ch-gray-700 mt-4 px-3 py-2 rounded shadow-md overflow-hidden text-ch-gray-300">
|
||||||
<ul className="bg-gray-200 mt-4 rounded shadow-md overflow-hidden">
|
<p className="text-lg">New Project</p>
|
||||||
{menuOptions.map(({ name, sub, ideType }) => (
|
<ul className="">
|
||||||
|
{menuOptions.map(({ name, sub, ideType, bgClasses, dotClasses }) => (
|
||||||
<li
|
<li
|
||||||
key={name}
|
key={name}
|
||||||
className="px-4 py-2 hover:bg-gray-400 text-gray-800"
|
className={
|
||||||
|
bgClasses +
|
||||||
|
' px-4 py-1 my-4 bg-opacity-30 hover:bg-opacity-70 grid grid-flow-col-dense items-center gap-2'
|
||||||
|
}
|
||||||
>
|
>
|
||||||
|
<div className={dotClasses + ' w-5 h-5 rounded-full'}></div>
|
||||||
<Link to={routes.draftProject({ cadPackage: ideType })}>
|
<Link to={routes.draftProject({ cadPackage: ideType })}>
|
||||||
<div>{name}</div>
|
<div>{name}</div>
|
||||||
<div className="text-xs text-gray-600 font-light">{sub}</div>
|
<div className="text-xs text-ch-gray-400 font-light">{sub}</div>
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ import ReactGA from 'react-ga'
|
|||||||
import Popover from '@material-ui/core/Popover'
|
import Popover from '@material-ui/core/Popover'
|
||||||
|
|
||||||
import useUser from 'src/helpers/hooks/useUser'
|
import useUser from 'src/helpers/hooks/useUser'
|
||||||
import ImageUploader from 'src/components/ImageUploader'
|
|
||||||
import LoginModal from 'src/components/LoginModal'
|
import LoginModal from 'src/components/LoginModal'
|
||||||
import Gravatar from 'src/components//Gravatar/Gravatar'
|
import Gravatar from 'src/components/Gravatar/Gravatar'
|
||||||
|
|
||||||
const ProfileSlashLogin = () => {
|
const ProfileSlashLogin = () => {
|
||||||
const { logOut, isAuthenticated, currentUser, client } = useAuth()
|
const { logOut, isAuthenticated, currentUser, client } = useAuth()
|
||||||
|
|||||||
59
app/web/src/components/ProjectCard/ProjectCard.tsx
Normal file
59
app/web/src/components/ProjectCard/ProjectCard.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { Link, routes } from '@redwoodjs/router'
|
||||||
|
import Svg from 'src/components/Svg/Svg'
|
||||||
|
import CadPackage from 'src/components/CadPackage/CadPackage'
|
||||||
|
|
||||||
|
import { countEmotes } from 'src/helpers/emote'
|
||||||
|
import ImageUploader from 'src/components/ImageUploader'
|
||||||
|
|
||||||
|
const ProjectCard = ({ title, mainImage, user, Reaction, cadPackage }) => (
|
||||||
|
<li
|
||||||
|
className="rounded p-1.5 bg-ch-gray-760 shadow-ch"
|
||||||
|
key={`${user?.userName}--${title}`}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
to={routes.project({
|
||||||
|
userName: user?.userName,
|
||||||
|
projectTitle: title,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className="relative">
|
||||||
|
<ImageUploader
|
||||||
|
className="rounded"
|
||||||
|
aspectRatio={1.4}
|
||||||
|
imageUrl={mainImage}
|
||||||
|
width={700}
|
||||||
|
/>
|
||||||
|
<CadPackage
|
||||||
|
cadPackage={cadPackage}
|
||||||
|
className="absolute right-0 top-0 p-1.5 rounded-bl text-sm bg-opacity-50"
|
||||||
|
dotClass="w-3 h-3"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center mt-1">
|
||||||
|
<div className="w-8 h-8 overflow-hidden rounded-full border border-ch-gray-300 shadow">
|
||||||
|
<ImageUploader
|
||||||
|
className=""
|
||||||
|
aspectRatio={1}
|
||||||
|
imageUrl={user?.image}
|
||||||
|
width={50}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="ml-3 text-lg text-ch-gray-300 font-fira-sans">
|
||||||
|
<div className="">{title}</div>
|
||||||
|
<div className="text-sm">{user?.userName}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-flow-col-dense gap-2 justify-start mt-1.5">
|
||||||
|
<div className="px-2 flex items-center bg-ch-gray-600 text-ch-gray-300 rounded-sm">
|
||||||
|
<Svg name="reactions" className="w-4 mr-2" />
|
||||||
|
{countEmotes(Reaction).reduce((prev, { count }) => prev + count, 0)}
|
||||||
|
</div>
|
||||||
|
<div className="px-2 flex items-center bg-ch-blue-650 bg-opacity-30 text-ch-gray-300 rounded-sm">
|
||||||
|
<Svg name="fork-new" className="w-4 mr-2" />0
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default ProjectCard
|
||||||
@@ -5,10 +5,12 @@ import CadPackage from 'src/components/CadPackage/CadPackage'
|
|||||||
|
|
||||||
import { countEmotes } from 'src/helpers/emote'
|
import { countEmotes } from 'src/helpers/emote'
|
||||||
import ImageUploader from 'src/components/ImageUploader'
|
import ImageUploader from 'src/components/ImageUploader'
|
||||||
|
import ProjectCard from 'src/components/ProjectCard/ProjectCard'
|
||||||
|
|
||||||
const ProjectsList = ({
|
const ProjectsList = ({
|
||||||
projects,
|
projects,
|
||||||
shouldFilterProjectsWithoutImage = false,
|
shouldFilterProjectsWithoutImage = false,
|
||||||
|
projectLimit = 80,
|
||||||
}) => {
|
}) => {
|
||||||
// temporary filtering projects that don't have images until some kind of search is added and there are more things on the website
|
// temporary filtering projects that don't have images until some kind of search is added and there are more things on the website
|
||||||
// it helps avoid the look of the website just being filled with dumby data.
|
// it helps avoid the look of the website just being filled with dumby data.
|
||||||
@@ -18,7 +20,9 @@ const ProjectsList = ({
|
|||||||
const filteredProjects = useMemo(
|
const filteredProjects = useMemo(
|
||||||
() =>
|
() =>
|
||||||
(shouldFilterProjectsWithoutImage
|
(shouldFilterProjectsWithoutImage
|
||||||
? projects.filter(({ mainImage }) => mainImage)
|
? projects
|
||||||
|
.filter(({ mainImage }) => mainImage)
|
||||||
|
.slice(0, projectLimit || 80)
|
||||||
: [...projects]
|
: [...projects]
|
||||||
)
|
)
|
||||||
// sort should probably be done on the service, but the filtering is temp too
|
// sort should probably be done on the service, but the filtering is temp too
|
||||||
@@ -30,64 +34,21 @@ const ProjectsList = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="max-w-6xl mx-auto">
|
<section className="max-w-7xl mx-auto">
|
||||||
<ul
|
<ul
|
||||||
className="grid gap-x-8 gap-y-12 items-center mx-4 relative"
|
className="grid gap-x-8 gap-y-8 items-center mx-4 relative"
|
||||||
style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(16rem, 1fr))' }}
|
style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(16rem, 1fr))' }}
|
||||||
>
|
>
|
||||||
{filteredProjects.map(
|
{filteredProjects.map(
|
||||||
({ title, mainImage, user, Reaction, cadPackage }) => (
|
({ title, mainImage, user, Reaction, cadPackage }, index) => (
|
||||||
<li
|
<ProjectCard
|
||||||
className="rounded p-1.5 bg-ch-gray-760 shadow-ch"
|
key={index}
|
||||||
key={`${user?.userName}--${title}`}
|
title={title}
|
||||||
>
|
mainImage={mainImage}
|
||||||
<Link
|
user={user}
|
||||||
to={routes.project({
|
Reaction={Reaction}
|
||||||
userName: user?.userName,
|
cadPackage={cadPackage}
|
||||||
projectTitle: title,
|
/>
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div className="relative">
|
|
||||||
<ImageUploader
|
|
||||||
className="rounded"
|
|
||||||
aspectRatio={1.4}
|
|
||||||
imageUrl={mainImage}
|
|
||||||
width={700}
|
|
||||||
/>
|
|
||||||
<CadPackage
|
|
||||||
cadPackage={cadPackage}
|
|
||||||
className="absolute right-0 top-0 p-1.5 rounded-bl text-sm bg-opacity-50"
|
|
||||||
dotClass="w-3 h-3"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center mt-1">
|
|
||||||
<div className="w-8 h-8 overflow-hidden rounded-full border border-ch-gray-300 shadow">
|
|
||||||
<ImageUploader
|
|
||||||
className=""
|
|
||||||
aspectRatio={1}
|
|
||||||
imageUrl={user?.image}
|
|
||||||
width={50}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="ml-3 text-lg text-ch-gray-300 font-fira-sans">
|
|
||||||
<div className="">{title}</div>
|
|
||||||
<div className="text-sm">{user?.userName}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-flow-col-dense gap-2 justify-start mt-1.5">
|
|
||||||
<div className="px-2 flex items-center bg-ch-gray-600 text-ch-gray-300 rounded-sm">
|
|
||||||
<Svg name="reactions" className="w-4 mr-2" />
|
|
||||||
{countEmotes(Reaction).reduce(
|
|
||||||
(prev, { count }) => prev + count,
|
|
||||||
0
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="px-2 flex items-center bg-ch-blue-650 bg-opacity-30 text-ch-gray-300 rounded-sm">
|
|
||||||
<Svg name="fork-new" className="w-4 mr-2" />0
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -37,12 +37,13 @@ export const Empty = () => {
|
|||||||
|
|
||||||
export const Success = ({
|
export const Success = ({
|
||||||
projects,
|
projects,
|
||||||
variables: { shouldFilterProjectsWithoutImage },
|
variables: { shouldFilterProjectsWithoutImage, projectLimit },
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Projects
|
<Projects
|
||||||
projects={projects}
|
projects={projects}
|
||||||
shouldFilterProjectsWithoutImage={shouldFilterProjectsWithoutImage}
|
shouldFilterProjectsWithoutImage={shouldFilterProjectsWithoutImage}
|
||||||
|
projectLimit={projectLimit}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export type SvgNames =
|
|||||||
| 'arrow-down'
|
| 'arrow-down'
|
||||||
// | 'arrow'
|
// | 'arrow'
|
||||||
| 'arrow-left'
|
| 'arrow-left'
|
||||||
|
| 'arrow-right'
|
||||||
| 'camera'
|
| 'camera'
|
||||||
| 'check'
|
| 'check'
|
||||||
| 'chevron-down'
|
| 'chevron-down'
|
||||||
@@ -74,6 +75,22 @@ const Svg = ({
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
|
'arrow-right': (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="h-6 w-6"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M14 5l7 7m0 0l-7 7m7-7H3"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
camera: (
|
camera: (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 21">
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 21">
|
||||||
<path
|
<path
|
||||||
|
|||||||
@@ -42,6 +42,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.three-debug-panel-1 {
|
||||||
|
position: fixed;
|
||||||
|
padding-top: 300px;
|
||||||
|
}
|
||||||
|
.three-debug-panel-2 {
|
||||||
|
position: fixed;
|
||||||
|
padding-top: 300px;
|
||||||
|
padding-left: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
.markdown-overrides h4 {
|
.markdown-overrides h4 {
|
||||||
@apply text-lg font-bold;
|
@apply text-lg font-bold;
|
||||||
|
|||||||
@@ -93,18 +93,18 @@ const MainLayout = ({ children, shouldRemoveFooterInIde }) => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<header id="cadhub-main-header">
|
<header id="cadhub-main-header">
|
||||||
<nav className="flex justify-between h-20 md:px-12 bg-gradient-to-r from-gray-900 to-indigo-900">
|
<nav className="flex justify-between h-16 sm:px-4 bg-ch-gray-900">
|
||||||
<ul className="flex items-center">
|
<ul className="flex items-center">
|
||||||
<li>
|
<li>
|
||||||
<Link to={routes.home()}>
|
<Link to={routes.home()}>
|
||||||
<div className="rounded-full overflow-hidden ml-2 md:ml-8">
|
<div className="rounded-full overflow-hidden ml-2">
|
||||||
<Svg className="w-10 md:w-16" name="favicon" />
|
<Svg className="w-10" name="favicon" />
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<Tooltip title="Very alpha, there's lots of work todo">
|
<Tooltip title="Very alpha, there's lots of work todo">
|
||||||
<div className="ml-2 md:ml-12 flex">
|
<div className="ml-4 flex">
|
||||||
{/* Because of how specific these styles are to this heading/logo and it doesn't need to be replicated else where as well as it's very precise with the placement of "pre-alpha" I think it's appropriate. */}
|
{/* Because of how specific these styles are to this heading/logo and it doesn't need to be replicated else where as well as it's very precise with the placement of "pre-alpha" I think it's appropriate. */}
|
||||||
<h2
|
<h2
|
||||||
className="text-indigo-300 text-2xl md:text-5xl font-ropa-sans py-1 md:tracking-wider"
|
className="text-indigo-300 text-2xl md:text-5xl font-ropa-sans py-1 md:tracking-wider"
|
||||||
@@ -125,7 +125,7 @@ const MainLayout = ({ children, shouldRemoveFooterInIde }) => {
|
|||||||
<ul className="flex items-center">
|
<ul className="flex items-center">
|
||||||
<li
|
<li
|
||||||
className={getActiveClasses(
|
className={getActiveClasses(
|
||||||
'mr-4 md:mr-8 h-8 w-8 md:h-10 md:w-10 rounded-full border-2 border-indigo-300 flex items-center justify-center'
|
'mr-1 sm:mr-4 md:mr-8 h-8 w-8 md:h-10 md:w-10 flex items-center justify-center'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<NavPlusButton />
|
<NavPlusButton />
|
||||||
@@ -150,10 +150,10 @@ const MainLayout = ({ children, shouldRemoveFooterInIde }) => {
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
className="text-indigo-200 font-semibold underline mr-2"
|
className="text-ch-gray-300 mr-1 sm:mr-2 px-2 sm:px-4 py-2 border-2 border-ch-gray-400 rounded-full hover:bg-ch-gray-600"
|
||||||
onClick={recordedLogin}
|
onClick={recordedLogin}
|
||||||
>
|
>
|
||||||
Sign in/up
|
Sign In/Up
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
@@ -173,18 +173,18 @@ const MainLayout = ({ children, shouldRemoveFooterInIde }) => {
|
|||||||
horizontal: 'right',
|
horizontal: 'right',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="p-4 w-48">
|
<div className="p-4 w-48 text-ch-gray-300">
|
||||||
<Link to={routes.user({ userName: user?.userName })}>
|
<Link to={routes.user({ userName: user?.userName })}>
|
||||||
<h3 className="text-indigo-800" style={{ fontWeight: '500' }}>
|
<h3 className="" style={{ fontWeight: '500' }}>
|
||||||
Hello {user?.name}
|
Hello {user?.name}
|
||||||
</h3>
|
</h3>
|
||||||
</Link>
|
</Link>
|
||||||
<hr />
|
<hr />
|
||||||
<br />
|
<br />
|
||||||
<Link to={routes.user({ userName: user?.userName })}>
|
<Link to={routes.user({ userName: user?.userName })}>
|
||||||
<div className="text-indigo-800">Your Profile</div>
|
<div className="">Your Profile</div>
|
||||||
</Link>
|
</Link>
|
||||||
<a href="#" className="text-indigo-800" onClick={logOut}>
|
<a href="#" className="" onClick={logOut}>
|
||||||
Logout
|
Logout
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
import MainLayout from 'src/layouts/MainLayout'
|
import MainLayout from 'src/layouts/MainLayout'
|
||||||
import ProjectsCell from 'src/components/ProjectsCell'
|
|
||||||
import LandingSection from 'src/components/LandingSection'
|
|
||||||
import Seo from 'src/components/Seo/Seo'
|
import Seo from 'src/components/Seo/Seo'
|
||||||
|
import { Hero } from 'src/components/Hero/Hero'
|
||||||
|
|
||||||
const ProjectsPage = () => {
|
const ProjectsPage = () => {
|
||||||
return (
|
return (
|
||||||
<MainLayout>
|
<MainLayout shouldRemoveFooterInIde>
|
||||||
<Seo
|
<Seo
|
||||||
title="Projects page"
|
title="Home page"
|
||||||
description="Cadhub Projects page"
|
description="Learn about Code CAD and the CadHub community"
|
||||||
lang="en-US"
|
lang="en-US"
|
||||||
|
socialImageUrl="https://cadhub.xyz/default-social-image.jpg"
|
||||||
/>
|
/>
|
||||||
<LandingSection />
|
<Hero />
|
||||||
<div className="bg-ch-gray-800 py-20">
|
|
||||||
<ProjectsCell shouldFilterProjectsWithoutImage />
|
|
||||||
</div>
|
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
24
app/web/src/pages/ProjectsPage/ProjectsPage.tsx
Normal file
24
app/web/src/pages/ProjectsPage/ProjectsPage.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import ProjectsCell from 'src/components/ProjectsCell'
|
||||||
|
import MainLayout from 'src/layouts/MainLayout'
|
||||||
|
import Seo from 'src/components/Seo/Seo'
|
||||||
|
|
||||||
|
const ProjectsPage = () => {
|
||||||
|
return (
|
||||||
|
<MainLayout shouldRemoveFooterInIde>
|
||||||
|
<Seo
|
||||||
|
title="Projects page"
|
||||||
|
description="Cadhub Projects page"
|
||||||
|
socialImageUrl="https://cadhub.xyz/default-social-image.jpg"
|
||||||
|
lang="en-US"
|
||||||
|
/>
|
||||||
|
<div className="bg-ch-gray-800 pb-64">
|
||||||
|
<h1 className="max-w-7xl mx-auto text-4xl px-4 py-4 pt-16 text-ch-gray-300 font-sans">
|
||||||
|
Projects
|
||||||
|
</h1>
|
||||||
|
<ProjectsCell shouldFilterProjectsWithoutImage projectLimit={80} />
|
||||||
|
</div>
|
||||||
|
</MainLayout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProjectsPage
|
||||||
@@ -15639,6 +15639,11 @@ react-image-crop@^8.6.6:
|
|||||||
clsx "^1.1.1"
|
clsx "^1.1.1"
|
||||||
prop-types "^15.7.2"
|
prop-types "^15.7.2"
|
||||||
|
|
||||||
|
react-intersection-observer@^8.32.1:
|
||||||
|
version "8.32.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-8.32.1.tgz#9b949871eb35eb1fc730732bbf8fcfaaaf3f5b02"
|
||||||
|
integrity sha512-FOmMkMw7MeJ8FkuADpU8TRcvGuTvPB+DRkaikS1QXcWArYLCWC3mjRorq2XeRGBuqmaueOBd27PUazTu9AgInw==
|
||||||
|
|
||||||
react-is@^16.7.0, react-is@^16.8.1:
|
react-is@^16.7.0, react-is@^16.8.1:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
||||||
|
|||||||
Reference in New Issue
Block a user