diff --git a/src/AppStore/AppThumbnail.jsx b/src/AppStore/AppThumbnail.jsx new file mode 100644 index 00000000..4717cc1a --- /dev/null +++ b/src/AppStore/AppThumbnail.jsx @@ -0,0 +1,91 @@ +const appDetailsUrl = `#/${REPL_ACCOUNT}/widget/ComponentDetailsPage?src=${props.author}/widget/${props.widgetName}`; + +const Thumbnail = styled.a` + display: block; + aspect-ratio: 1 / 1; + overflow: hidden; + border-radius: 1.25rem; + border: 1px solid var(--sand6); + position: relative; + cursor: pointer; + text-decoration: none !important; + outline: none; + transition: all 200ms; + + img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; + border: none; + } + + &:hover, + &:focus { + box-shadow: 0 0 15px rgba(0, 0, 0, 0.15); + } +`; + +const ThumbnailContent = styled.span` + display: flex; + flex-direction: column; + gap: 0.25rem; + position: absolute; + bottom: 0; + left: 0; + right: 0; + padding: 1.25rem; + padding-top: 3.5rem; + background: linear-gradient( + to top, + rgba(0, 0, 0, 0.85) 20%, + rgba(0, 0, 0, 0) + ); + font: var(--text-xs); + color: var(--white); + text-shadow: 0 0 2px rgba(0, 0, 0, 0.75); + + b { + font-weight: 600; + } + + @media (max-width: 650px) { + padding: 0.75rem; + } +`; + +const ThumbnailTag = styled.span` + display: inline-flex; + border-bottom-right-radius: 1.25rem; + background: var(--violet7); + color: #fff; + font: var(--text-xs); + font-weight: 700; + padding: 0.25rem 0.75rem; + position: absolute; + top: 0; + left: 0; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + border-right: 1px solid rgba(0, 0, 0, 0.1); +`; + +return ( + + + + + + {props.name ?? props.widgetName} + + {props.author} + + +); diff --git a/src/AppStore/ArticleSummary.jsx b/src/AppStore/ArticleSummary.jsx new file mode 100644 index 00000000..9cdeba14 --- /dev/null +++ b/src/AppStore/ArticleSummary.jsx @@ -0,0 +1,68 @@ +const Text = styled.p` + font: var(--${(p) => p.size ?? "text-base"}); + font-weight: ${(p) => p.fontWeight}; + color: var(--${(p) => p.color ?? "sand12"}); + margin: 0; +`; + +const ArticleImage = styled.div` + width: 100%; + aspect-ratio: 26 / 15; + border-radius: 1rem; + border: 1px solid var(--sand6); + overflow: hidden; + transition: all 200ms; + + img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; + border: none; + } +`; + +const ArticleContent = styled.div` + display: flex; + flex-direction: column; + gap: 0.25rem; +`; + +const Article = styled.a` + display: flex; + flex-direction: column; + gap: 1rem; + align-items: row; + cursor: pointer; + text-decoration: none !important; + + &:hover, + &:focus { + ${ArticleImage} { + box-shadow: 0 0 15px rgba(0, 0, 0, 0.15); + } + } +`; + +return ( +
+ + + + + + + {props.title} + + {props.author} + +
+); diff --git a/src/AppStore/IndexPage.jsx b/src/AppStore/IndexPage.jsx new file mode 100644 index 00000000..b10098b1 --- /dev/null +++ b/src/AppStore/IndexPage.jsx @@ -0,0 +1,301 @@ +State.init({ + categories: [], + isLoading: true, + selectedTab: props.tab, +}); + +if (props.tab && props.tab !== state.selectedTab) { + State.update({ + selectedTab: props.tab, + }); +} + +const appStoreIndexUrl = "#/${REPL_ACCOUNT}/widget/AppStore.IndexPage"; +const selectedCategory = state.categories.find( + (category) => category.label === state.selectedTab +); + +function loadData() { + if (state.categories.length > 0) return; + + asyncFetch( + "https://storage.googleapis.com/databricks-near-query-runner/output/app-store.json" + ) + .then((res) => { + State.update({ + categories: res.body.categories, + isLoading: false, + selectedTab: state.selectedTab ?? res.body.categories[0].label, + }); + }) + .catch((error) => { + State.update({ + isLoading: false, + }); + console.log(error); + }); +} + +loadData(); + +const Wrapper = styled.div` + padding: 100px 0; + background: url("https://ipfs.near.social/ipfs/bafkreie5t75jirebnuyozmsc5hxzhxpoqivaxmc4rypaaogab6qh7asb2i"); + background-position: right top; + background-size: 1440px auto; + background-repeat: no-repeat; + margin-top: calc(var(--body-top-padding) * -1); + + @media (max-width: 1024px) { + padding: 50px 0; + } + + @media (max-width: 800px) { + background-image: none; + padding: 2rem 0; + } +`; + +const Container = styled.div` + max-width: 1120px; + margin: 0 auto; + padding: 0 16px; +`; + +const Main = styled.div` + display: flex; + gap: 6.5rem; + + @media (max-width: 1024px) { + flex-direction: column; + gap: 3rem; + } + + @media (max-width: 800px) { + gap: 2rem; + } +`; + +const Menu = styled.div` + width: 7.5rem; + flex-shrink: 0; + text-align: right; + display: flex; + flex-direction: column; + gap: 1.5rem; + overflow: auto; + scroll-behavior: smooth; + + @media (max-width: 1024px) { + width: 100%; + text-align: left; + flex-direction: row; + } +`; + +const MenuLink = styled.a` + display: block; + font: var(--text-s); + font-weight: 600; + color: var(--sand12); + outline: none; + + &:hover, + &:focus { + color: var(--sand12); + text-decoration: underline; + } + + &[data-active="true"] { + color: var(--violet7); + } +`; + +const Sections = styled.div` + display: flex; + flex-direction: column; + gap: 3rem; + flex-grow: 1; +`; + +const Section = styled.div``; + +const H1 = styled.h1` + font: var(--text-hero); + color: var(--sand12); + margin: 0 0 3rem; + padding-left: 14rem; + + @media (max-width: 1024px) { + padding-left: 0; + } + + @media (max-width: 800px) { + font: var(--text-3xl); + font-weight: 600; + margin: 0 0 2rem; + } +`; + +const H2 = styled.h2` + font: var(--text-l); + color: var(--sand12); + margin: 0 0 1.5rem; + font-weight: 600; +`; + +const Text = styled.p` + font: var(--${(p) => p.size ?? "text-base"}); + font-weight: ${(p) => p.fontWeight}; + color: var(--${(p) => p.color ?? "sand12"}); + margin: 0; +`; + +const ThumbnailGrid = styled.div` + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr; + gap: 1.5rem; + + @media (max-width: 850px) { + grid-template-columns: 1fr 1fr 1fr; + } + + @media (max-width: 550px) { + gap: 0.5rem; + grid-template-columns: 1fr 1fr; + } +`; + +const ContentGrid = styled.div` + display: grid; + grid-template-columns: 1fr 1fr; + gap: 2rem; + + @media (max-width: 650px) { + grid-template-columns: 1fr; + } +`; + +return ( + + +

+ {state.selectedTab === "Search" ? "Search" : selectedCategory?.title} +

+ +
+ + {state.categories.map((category) => { + return ( + + {category.label} + + ); + })} + + + Search + + + + + {state.selectedTab === "Search" && ( + + )} + + {state.selectedTab !== "Search" && selectedCategory && ( + <> + {selectedCategory.sections.map((section) => { + switch (section.format) { + case "MEDIUM": + return ( +
+ {section.title &&

{section.title}

} + + {section.items.map((item) => { + return ( + + ); + })} + +
+ ); + + case "SMALL": + return ( +
+ {section.title &&

{section.title}

} + + + {section.items.map((item) => { + return ( + + ); + })} + +
+ ); + + case "ARTICLE": + return ( +
+ {section.title &&

{section.title}

} + + + {section.items.map((item) => { + return ( + + ); + })} + +
+ ); + + default: + return null; + } + })} + + )} +
+
+
+
+); diff --git a/src/AppStore/Search.jsx b/src/AppStore/Search.jsx new file mode 100644 index 00000000..a9b24847 --- /dev/null +++ b/src/AppStore/Search.jsx @@ -0,0 +1,156 @@ +function search(query) { + if (!query) { + State.update({ + isLoading: false, + results: [], + }); + return; + } + + const body = { + query, + page: state.currentPage, + filters: "categories:widget AND tags:app AND NOT _tags:hidden", + }; + + asyncFetch("/api/search", { + body: JSON.stringify(body), + headers: { + "Content-Type": "application/json", + }, + method: "POST", + }) + .then((res) => { + State.update({ + isLoading: false, + results: [...state.results, ...res.body.hits], + totalPages: res.body.nbPages, + }); + }) + .catch((error) => { + State.update({ + isLoading: false, + }); + console.log(error); + }); +} + +function handleOnInput() { + State.update({ + currentPage: 0, + totalPages: 0, + isLoading: true, + results: [], + }); +} + +function handleOnQueryChange(query) { + State.update({ + query, + }); + search(query); +} + +function loadMore() { + State.update({ + currentPage: state.currentPage + 1, + isLoading: true, + }); + + search(state.query); +} + +State.init({ + currentPage: 0, + totalPages: 0, + isLoading: false, + query: "", + results: [], +}); + +const Wrapper = styled.div` + display: flex; + width: 100%; + flex-direction: column; + gap: 3rem; +`; + +const H2 = styled.h2` + font: var(--text-l); + color: var(--sand12); + margin: 0; + font-weight: 600; +`; + +const Text = styled.p` + font: var(--${(p) => p.size ?? "text-base"}); + font-weight: ${(p) => p.fontWeight}; + color: var(--${(p) => p.color ?? "sand12"}); + margin: 0; +`; + +const ContentGrid = styled.div` + display: grid; + grid-template-columns: 1fr 1fr; + gap: 2rem; + + @media (max-width: 650px) { + grid-template-columns: 1fr; + } +`; + +return ( + + + + {state.query && state.results.length === 0 && !state.isLoading && ( + No apps matched your search: "{state.query}" + )} + + {state.results.length > 0 && ( + <> +

All apps

+ + + {state.results.map((result) => { + return ( + + ); + })} + + + )} + + {state.currentPage + 1 < state.totalPages && ( + + )} +
+); diff --git a/src/ComponentCard.jsx b/src/ComponentCard.jsx index 6b1ae9a7..b17d85c5 100644 --- a/src/ComponentCard.jsx +++ b/src/ComponentCard.jsx @@ -1,12 +1,14 @@ -const [accountId, widget, widgetName] = props.src.split("/"); -const metadata = Social.get( - `${accountId}/widget/${widgetName}/metadata/**`, - "final" -); -const tags = Object.keys(metadata.tags || {}); +const [accountId, unused, widgetName] = props.src.split("/"); const detailsUrl = `#/${REPL_ACCOUNT}/widget/ComponentDetailsPage?src=${accountId}/widget/${widgetName}`; const appUrl = `#/${accountId}/widget/${widgetName}`; const accountUrl = `#/${REPL_ACCOUNT}/widget/ProfilePage?accountId=${accountId}`; +const metadata = + props.metadata ?? + Social.get(`${accountId}/widget/${widgetName}/metadata/**`, "final") ?? + {}; +const tags = props.metadata + ? props.metadata.tags + : Object.keys(metadata.tags || {}); const Card = styled.div` position: relative; diff --git a/src/ComponentDetailsPage.jsx b/src/ComponentDetailsPage.jsx index 46ac5114..543472b2 100644 --- a/src/ComponentDetailsPage.jsx +++ b/src/ComponentDetailsPage.jsx @@ -1,6 +1,6 @@ State.init({ copiedShareUrl: false, - selectedTab: props.tab ?? "source", + selectedTab: props.tab ?? "about", }); if (props.tab && props.tab !== state.selectedTab) { diff --git a/src/DIG/Accordion.jsx b/src/DIG/Accordion.jsx index 6cab8232..87ab0f41 100644 --- a/src/DIG/Accordion.jsx +++ b/src/DIG/Accordion.jsx @@ -15,7 +15,7 @@ const Item = styled("Accordion.Item")` `; const Header = styled("Accordion.Header")` - margin: 0; + margin: 0 !important; `; const Trigger = styled("Accordion.Trigger")` diff --git a/src/DIG/Button.metadata.json b/src/DIG/Button.metadata.json index 5a6efebf..a9c71154 100644 --- a/src/DIG/Button.metadata.json +++ b/src/DIG/Button.metadata.json @@ -1,7 +1,7 @@ { - "description": "A fully featured button component that can act as a `