import { Button , Chip , makeStyles , Typography } from "@material-ui/core" ; import { GetServerSideProps } from "next" ; import { useRouter } from "next/dist/client/router" ; import Head from "next/head" ; import { useMemo , useState } from "react" ; import { useAuthState } from "@kobra-dev/react-firebase-auth-hooks/auth" ; import Loader from "../../components/Loader" ; import PageLayout from "../../components/PageLayout" ; import { GetProjectDetailsDocument , GetProjectDetailsQuery , GetProjectDetailsQueryVariables , ProjectDetailsFragment , useEditProjectDetailsMutation , useGetProjectDetailsLazyQuery , GetProjectDetailsUserProjectsQuery , GetProjectDetailsUserProjectsQueryVariables , GetProjectDetailsUserProjectsDocument } from "../../generated/queries" ; import { initializeApollo } from "../../utils/apolloClient" ; import Error404 from "../404" ; import firebase from "../../utils/firebase" ; import { AccountCircle , AccountTree , CalendarToday , Launch , Lock , Public } from "@material-ui/icons" ; import { formatDateString } from "../../utils/misc" ; import Stack from "../../components/Stack" ; import Description from "src/components/project/Description" ; import EditableTitle from "src/components/EditableTitle" ; import { MAX_NAME_LEN , MAX_SUMMARY_LEN } from "src/utils/constants" ; import ProjectCard from "src/components/project/ProjectCard" ; import NetworkModal from "src/components/project/NetworkModal" ; import CardGrid from "src/components/CardGrid" ; import Link from "next/link" ; interface ProjectProps { project : ProjectDetailsFragment | null ; } const useStyles = makeStyles ( ( theme ) => ( { header : { display : "flex" , "& > *:first-child" : { flex : 1 } } , addButtonWrapper : { marginTop : "auto" , marginBottom : "auto" } , visibilityButton : { marginRight : "1rem" } , w100 : { width : "100%" } } ) ) ; const SUMMARY_PLACEHOLDER_TEXT = "No summary provided" ; const DESCRIPTION_PLACEHOLDER_TEXT = "No description provided" ; export default function Project ( props ) { const styles = useStyles ( ) ; const router = useRouter ( ) ; const [ getProjectData , { data , loading , error } ] = useGetProjectDetailsLazyQuery ( { variables : { id : router . query . id as string } } ) ; const [ editProjectDetails , { loading : editLoading } ] = useEditProjectDetailsMutation ( ) ; const [ user , userLoading ] = useAuthState ( firebase . auth ( ) ) ; const [ networkOpen , setNetworkOpen ] = useState ( false ) ; useMemo ( ( ) => { if ( ! props . project ) { getProjectData ( ) ; } } , [ userLoading ] ) ; if ( ( error && ! loading && ! ( data || props . project ) && ! userLoading ) || ( data ?. project && ! data . project . isPublic && ! user ) ) return < Error404 />; if ( ! ( data || props . project ) || editLoading ) return < Loader />; const proj = data ?. project ?? props . project ; if ( ! proj ) throw new Error ( "Project is undefined/null (this shouldn't ever happen)" ) ; const otherUserProjects = proj . user . projects . filter ( ( otherProj ) => otherProj . id !== proj . id ) . slice ( 0 , 3 ) ; return ( < > < Head > < title > { proj . name } | Kobra < /title> < /Head> < PageLayout > < Stack direction = "column" > < div className = { styles . header } > < Typography variant = "h2" color = "textPrimary" > { proj . parent && ( < Typography variant = "h6" color = "textPrimary" > Fork of { " " } < Link href = { "/project/" + proj . parent . id } > { proj . parent . name } < /Link>{" "} by { " " } < Link href = { "/user/" + proj . parent . user . name } > { proj . parent . user . name } < /Link> < /Typography>)} { proj . userId === user ?. uid ? ( < EditableTitle className = { styles . w100 } size = "lg" maxLength = { MAX_NAME_LEN } value = { proj . name } onChange = { async ( name ) => { if ( name === proj . name ) return ; await editProjectDetails ( { variables : { id : router . query . id as string , name } } ) ; getProjectData ( ) ; } } />) : (proj.name)} < /Typography> < div className = { styles . addButtonWrapper } > { proj . userId === user ?. uid && ( < Button className = { styles . visibilityButton } size = "large" variant = "contained" color = "secondary" onClick = { async ( ) => { await editProjectDetails ( { variables : { id : router . query . id as string , isPublic : ! proj . isPublic } , async update ( cache ) { const client = initializeApollo ( ) ; const idsOfProjectsToUpdate = Object . entries ( cache . extract ( ) ) . filter ( ( cacheItem ) => cacheItem [ 0 ] . startsWith ( "Project:" ) && cacheItem [ 1 ] ?. userId === user . uid && cacheItem [ 1 ] . user ) . map ( ( cacheItem ) => cacheItem [ 0 ] ) ; if ( idsOfProjectsToUpdate . length === 0 ) return ; const newUserData = ! proj . isPublic ? [ { id : proj . id , name : proj . name , description : proj . description , updatedAt : new Date ( ) . toString ( ) , isPublic : true } , ... proj . user . projects . slice ( 0 , 3 ) ] : ( await client . query < GetProjectDetailsUserProjectsQuery , GetProjectDetailsUserProjectsQueryVariables > ( { query : GetProjectDetailsUserProjectsDocument , variables : { userId : user . uid } } ) ) . data . projects ; idsOfProjectsToUpdate . forEach ( ( id ) => { cache . modify ( { id , fields : { user : ( cachedUser ) => ( { ... cachedUser , 'projects({"isPublic":true,"sortByNewest":true,"take":4})' : newUserData } ) } } ) ; } ) ; } } ) ; getProjectData ( ) ; } } > { proj . isPublic ? "Unpublish" : "Publish" } < /Button>)} < Button size = "large" variant = "contained" color = "primary" startIcon = { < Launch / > } onClick = { ( ) => router . push ( "/editor?id=" + router . query . id ) } > Open in Studio < /Button> < /div> < /div> { ( proj . summary || proj . userId === user ?. uid ) && ( < Typography variant = "h5" color = "textPrimary" > { proj . userId === user ?. uid ? ( < EditableTitle className = { styles . w100 } size = "md" placeholder = { SUMMARY_PLACEHOLDER_TEXT } maxLength = { MAX_SUMMARY_LEN } value = { proj . summary ?? "" } onChange = { async ( summary ) => { if ( summary === proj . summary ) return ; await editProjectDetails ( { variables : { id : router . query . id as string , summary } } ) ; getProjectData ( ) ; } } />) : (proj.summary ?? SUMMARY_PLACEHOLDER_TEXT)} < /Typography>)} < Stack direction = "row" spacing = "0.5rem" > < Chip variant = "outlined" icon = { < AccountCircle / > } label = { proj . user . name } onClick = { ( ) => { router . push ( "/user/" + proj . user . name ) ; } } /> < Chip variant = "outlined" icon = { < CalendarToday / > } label = { `Last modified ${ formatDateString ( proj . updatedAt ) }, created ${ formatDateString ( proj . createdAt ) }` } /> { proj . userId === user ?. uid && ( < Chip variant = "outlined" icon = { proj . isPublic ? < Public /> : <Lock / > } label = { proj . isPublic ? "Public" : "Private" } / > ) } < Chip variant = "outlined" icon = { < AccountTree / > } label = { ( proj . children ?. length ?? 0 ) + " fork" + ( proj . children ?. length !== 1 ? "s" : "" ) } onClick = { proj . children ?. length !== 0 ? ( ) => setNetworkOpen ( true ) : undefined } / > < /Stack> < Description description = { proj . description ?? undefined } placeholder = { DESCRIPTION_PLACEHOLDER_TEXT } canEdit = { proj . userId === user ?. uid } onSave = { async ( description ) => { await editProjectDetails ( { variables : { id : router . query . id as string , description : description ?? "" } } ) ; getProjectData ( ) ; } } /> { otherUserProjects . length > 0 && ( < > < Typography variant = "h4" color = "textPrimary" > Other projects by { proj . user . name } < /Typography> < CardGrid > { otherUserProjects . map ( ( otherProj ) => ( < ProjectCard key = { otherProj . id } proj = { otherProj } / > ) ) } < /CardGrid> < />)} < /Stack> < NetworkModal proj = { proj } open = { networkOpen } onClose = { ( ) => setNetworkOpen ( false ) } /> < /PageLayout> < />); } export const getServerSideProps = async ( context ) => { const apolloClient = initializeApollo ( ) ; if ( context . params ?. id && ! Array . isArray ( context . params . id ) ) { let project = undefined ; try { const { data } = await apolloClient . query < GetProjectDetailsQuery , GetProjectDetailsQueryVariables > ( { query : GetProjectDetailsDocument , variables : { id : context . params . id } } ) ; project = data . project ; } catch ( err ) { } return { props : { project : project ?? null } } ; } return { notFound : true } ; } ;