diff --git a/packages/app-root/src/components/HomePageContainer.js b/packages/app-root/src/components/HomePageContainer.js
index 5a79fc719c..07ed683046 100644
--- a/packages/app-root/src/components/HomePageContainer.js
+++ b/packages/app-root/src/components/HomePageContainer.js
@@ -4,6 +4,7 @@ import { useContext } from 'react'
import { Box } from 'grommet'
import { PanoptesAuthContext } from '../contexts'
import { CommunityContainer, DefaultHome } from '@zooniverse/content'
+import { UserHome } from '@zooniverse/user'
import { Loader } from '@zooniverse/react-components'
export default function HomePageContainer({
@@ -20,7 +21,7 @@ export default function HomePageContainer({
) : (
- {user?.login ? Signed-in
: }
+ {user?.login ? : }
)}
+
+
+ )
+}
+
+export default UserHome
+
+UserHome.propTypes = {
+ authUser: shape({
+ id: string
+ })
+}
diff --git a/packages/lib-user/src/components/UserHome/components/RecentSubjects/RecentSubjects.js b/packages/lib-user/src/components/UserHome/components/RecentSubjects/RecentSubjects.js
new file mode 100644
index 0000000000..c480db4b23
--- /dev/null
+++ b/packages/lib-user/src/components/UserHome/components/RecentSubjects/RecentSubjects.js
@@ -0,0 +1,92 @@
+import { Anchor, Box, ResponsiveContext, Text } from 'grommet'
+import { arrayOf, bool, shape, string } from 'prop-types'
+import { useContext } from 'react'
+import { Loader, SpacedHeading, SpacedText } from '@zooniverse/react-components'
+
+import SubjectCard from '../SubjectCard/SubjectCard.js'
+
+function RecentSubjects({
+ isLoading = false,
+ recents = [],
+ recentsError = undefined
+}) {
+ const size = useContext(ResponsiveContext)
+
+ return (
+
+
+ Recent Classifications
+
+
+ {isLoading && (
+
+
+
+ )}
+ {!isLoading && recentsError && (
+
+
+ There was an error fetching recent classifications
+
+
+ )}
+ {!isLoading && !recents?.length && !recentsError && (
+
+ No Recent Classifications found
+
+ Start by{' '}
+
+ classifying any project
+ {' '}
+ to show your recent classifications here.
+
+
+ )}
+ {!isLoading && recents?.length
+ ? recents.slice(0, 10).map(classification => {
+ const subjectMedia = classification.locations.map(
+ location => Object.values(location)[0]
+ )
+ return (
+
+ )
+ })
+ : null}
+
+
+ )
+}
+
+export default RecentSubjects
+
+RecentSubjects.propTypes = {
+ isLoading: bool,
+ recents: arrayOf(
+ shape({
+ id: string,
+ slug: string
+ })
+ )
+}
diff --git a/packages/lib-user/src/components/UserHome/components/RecentSubjects/RecentSubjects.stories.js b/packages/lib-user/src/components/UserHome/components/RecentSubjects/RecentSubjects.stories.js
new file mode 100644
index 0000000000..919dffafa8
--- /dev/null
+++ b/packages/lib-user/src/components/UserHome/components/RecentSubjects/RecentSubjects.stories.js
@@ -0,0 +1,26 @@
+import RecentSubjects from './RecentSubjects'
+import mockRecents from './recents.mock.json'
+
+export default {
+ title: 'Components / UserHome / RecentSubjects',
+ component: RecentSubjects
+}
+
+export const Default = {
+ args: {
+ recents: mockRecents
+ }
+}
+
+export const NoSubjects = {
+ args: {
+ recents: []
+ }
+}
+
+export const Error = {
+ args: {
+ recents: [],
+ recentsError: { message: `Couldn't fetch recent classifications` }
+ }
+}
diff --git a/packages/lib-user/src/components/UserHome/components/RecentSubjects/RecentSubjectsContainer.js b/packages/lib-user/src/components/UserHome/components/RecentSubjects/RecentSubjectsContainer.js
new file mode 100644
index 0000000000..1cef223225
--- /dev/null
+++ b/packages/lib-user/src/components/UserHome/components/RecentSubjects/RecentSubjectsContainer.js
@@ -0,0 +1,37 @@
+import useSWR from 'swr'
+import { shape, string } from 'prop-types'
+
+import fetchRecentSubjects from './fetchRecentSubjects.js'
+import RecentSubjects from './RecentSubjects.js'
+
+const SWROptions = {
+ revalidateIfStale: true,
+ revalidateOnMount: true,
+ revalidateOnFocus: true,
+ revalidateOnReconnect: true,
+ refreshInterval: 0
+}
+
+function RecentSubjectsContainer({ authUser }) {
+ const cacheKey = {
+ name: 'user-recent-classifications',
+ userId: authUser.id
+ }
+ const {
+ data: recents,
+ error: recentsError,
+ isLoading
+ } = useSWR(cacheKey, fetchRecentSubjects, SWROptions)
+
+ return (
+
+ )
+}
+
+export default RecentSubjectsContainer
+
+RecentSubjectsContainer.propTypes = {
+ authUser: shape({
+ id: string
+ })
+}
diff --git a/packages/lib-user/src/components/UserHome/components/RecentSubjects/fetchRecentSubjects.js b/packages/lib-user/src/components/UserHome/components/RecentSubjects/fetchRecentSubjects.js
new file mode 100644
index 0000000000..bb6058b92c
--- /dev/null
+++ b/packages/lib-user/src/components/UserHome/components/RecentSubjects/fetchRecentSubjects.js
@@ -0,0 +1,57 @@
+import { panoptes } from '@zooniverse/panoptes-js'
+import auth from 'panoptes-client/lib/auth'
+
+async function fetchUserRecents() {
+ try {
+ const user = await auth.checkCurrent()
+ const token = await auth.checkBearerToken()
+ const authorization = `Bearer ${token}`
+ const query = {
+ page: 1,
+ sort: '-created_at'
+ }
+ const response = await panoptes.get(`/users/${user.id}/recents`, query, {
+ authorization
+ })
+ return response.body?.recents
+ } catch (error) {
+ console.error(error)
+ return []
+ }
+}
+
+/* Anatomy of a classification object */
+// [
+// {
+// id: "258337208",
+// links: {
+// project: "19072",
+// workflow: "22152",
+// subject: "80286011"
+// },
+// ...
+// },
+// ...
+// ]
+
+async function fetchSubjectLinks(classification) {
+ try {
+ const response = await panoptes.get('/projects', {
+ id: classification.links?.project
+ })
+ const slug = response.body?.projects[0].slug
+ classification.slug = slug
+ } catch {
+ console.error(error)
+ }
+}
+
+export default async function fetchRecentSubjects() {
+ const recents = await fetchUserRecents()
+
+ if (recents?.length) {
+ await Promise.allSettled(recents.map(fetchSubjectLinks))
+ }
+
+ return recents
+}
diff --git a/packages/lib-user/src/components/UserHome/components/RecentSubjects/recents.mock.json b/packages/lib-user/src/components/UserHome/components/RecentSubjects/recents.mock.json
new file mode 100644
index 0000000000..8e36d3de72
--- /dev/null
+++ b/packages/lib-user/src/components/UserHome/components/RecentSubjects/recents.mock.json
@@ -0,0 +1,358 @@
+[
+ {
+ "id": "67816",
+ "created_at": "2024-05-22T22:40:49.416Z",
+ "updated_at": "2024-05-22T22:40:49.416Z",
+ "locations": [
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/d152793b-dfbd-4c92-9f65-199958ae6370.jpeg"
+ }
+ ],
+ "href": "/users/1326053/recents/67816",
+ "links": { "project": "2000", "workflow": "3779", "subject": "251838" },
+ "slug": "markb-panoptes/pizza-please"
+ },
+ {
+ "id": "67815",
+ "created_at": "2024-05-22T22:40:48.818Z",
+ "updated_at": "2024-05-22T22:40:48.818Z",
+ "locations": [
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/15d79f4e-e1e4-4338-b553-525c4206fba8.jpeg"
+ }
+ ],
+ "href": "/users/1326053/recents/67815",
+ "links": { "project": "2000", "workflow": "3779", "subject": "251895" },
+ "slug": "markb-panoptes/pizza-please"
+ },
+ {
+ "id": "67814",
+ "created_at": "2024-05-22T22:40:48.630Z",
+ "updated_at": "2024-05-22T22:40:48.630Z",
+ "locations": [
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/166fec58-ac55-4b73-8507-b7df7882d763.jpeg"
+ }
+ ],
+ "href": "/users/1326053/recents/67814",
+ "links": { "project": "2000", "workflow": "3779", "subject": "251863" },
+ "slug": "markb-panoptes/pizza-please"
+ },
+ {
+ "id": "67812",
+ "created_at": "2024-05-21T20:27:04.652Z",
+ "updated_at": "2024-05-21T20:27:04.652Z",
+ "locations": [
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/13ef7bfe-50df-411b-896b-58edd6eefb7e.jpeg"
+ }
+ ],
+ "href": "/users/1326053/recents/67812",
+ "links": { "project": "2000", "workflow": "3779", "subject": "251824" },
+ "slug": "markb-panoptes/pizza-please"
+ },
+ {
+ "id": "67811",
+ "created_at": "2024-05-21T20:27:02.673Z",
+ "updated_at": "2024-05-21T20:27:02.673Z",
+ "locations": [
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/dbf409f9-b8d6-4f6c-8a45-125cb86e0e9e.jpeg"
+ }
+ ],
+ "href": "/users/1326053/recents/67811",
+ "links": { "project": "2000", "workflow": "3779", "subject": "251993" },
+ "slug": "markb-panoptes/pizza-please"
+ },
+ {
+ "id": "67810",
+ "created_at": "2024-05-21T20:27:00.712Z",
+ "updated_at": "2024-05-21T20:27:00.712Z",
+ "locations": [
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/8b81f908-62f7-45e3-8b23-6d4365ff2b4c.jpeg"
+ }
+ ],
+ "href": "/users/1326053/recents/67810",
+ "links": { "project": "2000", "workflow": "3779", "subject": "251816" },
+ "slug": "markb-panoptes/pizza-please"
+ },
+ {
+ "id": "67809",
+ "created_at": "2024-05-21T20:26:59.826Z",
+ "updated_at": "2024-05-21T20:26:59.826Z",
+ "locations": [
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/38b527d7-34f7-42d2-b080-960ef8af6bca.jpeg"
+ }
+ ],
+ "href": "/users/1326053/recents/67809",
+ "links": { "project": "2000", "workflow": "3779", "subject": "251854" },
+ "slug": "markb-panoptes/pizza-please"
+ },
+ {
+ "id": "67704",
+ "created_at": "2024-04-23T17:21:03.695Z",
+ "updated_at": "2024-04-23T17:21:03.695Z",
+ "locations": [
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/8f4b2ab9-19a7-4dec-b1fa-04eb2d69a584.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/9ef55eb3-bfcf-4187-8b9c-0bcee97b1924.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/62bfe845-b780-4ef6-9166-e9990e79ae80.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/00be850a-c369-470f-b2ab-cbe83baabeda.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/68952455-c3ef-49d4-ae4a-5659089af66c.jpeg"
+ }
+ ],
+ "href": "/users/1326053/recents/67704",
+ "links": { "project": "1993", "workflow": "3760", "subject": "251785" },
+ "slug": "kieftrav/freehand-line-multiframe"
+ },
+ {
+ "id": "67591",
+ "created_at": "2024-03-06T01:40:46.230Z",
+ "updated_at": "2024-03-06T01:40:46.230Z",
+ "locations": [
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/eb848228-fb50-4765-8f89-9f324f2a49ad.jpeg"
+ }
+ ],
+ "href": "/users/1326053/recents/67591",
+ "links": { "project": "1233", "workflow": "3480", "subject": "48416" },
+ "slug": "srallen086/srallen-testing"
+ },
+ {
+ "id": "67590",
+ "created_at": "2024-03-06T01:34:55.207Z",
+ "updated_at": "2024-03-06T01:34:55.207Z",
+ "locations": [
+ {
+ "text/plain": "https://panoptes-uploads-staging.zooniverse.org/subject_location/a6158f09-80c0-477d-962c-625dbae1d65a.txt"
+ }
+ ],
+ "href": "/users/1326053/recents/67590",
+ "links": { "project": "1952", "workflow": "3759", "subject": "136324" },
+ "slug": "markb-panoptes/digileap-testing-staging"
+ },
+ {
+ "id": "67589",
+ "created_at": "2024-03-06T01:34:47.508Z",
+ "updated_at": "2024-03-06T01:34:47.508Z",
+ "locations": [
+ {
+ "text/plain": "https://panoptes-uploads-staging.zooniverse.org/subject_location/9cd82380-5c44-40ef-a0da-dcafb01195e1.txt"
+ }
+ ],
+ "href": "/users/1326053/recents/67589",
+ "links": { "project": "1952", "workflow": "3759", "subject": "136325" },
+ "slug": "markb-panoptes/digileap-testing-staging"
+ },
+ {
+ "id": "67585",
+ "created_at": "2024-03-04T23:17:59.932Z",
+ "updated_at": "2024-03-04T23:17:59.932Z",
+ "locations": [
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/9d5e6636-0c19-4cc8-9d9b-c831e1ff46a5.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/02898526-1558-43a0-b9a2-5206209f08c6.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/b4a4f64b-3537-4f3c-9c46-09f6e885bae1.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/0a0a9463-fb61-4692-8c58-c380bf16cb00.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/b66b4729-1dfd-421a-a017-ac63ce219851.jpeg"
+ }
+ ],
+ "href": "/users/1326053/recents/67585",
+ "links": { "project": "1993", "workflow": "3783", "subject": "251786" },
+ "slug": "kieftrav/freehand-line-multiframe"
+ },
+ {
+ "id": "67584",
+ "created_at": "2024-03-04T23:17:15.590Z",
+ "updated_at": "2024-03-04T23:17:15.590Z",
+ "locations": [
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/2f5afbaf-5962-413e-8af6-f3ca0add4ac6.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/2dc8f960-0867-449e-aad9-422291d4b812.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/5c3fe925-ec47-4f49-b3dc-d83005220fd3.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/92050e9f-4b33-4aea-9232-d3ba0d929ff0.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/02d4ea5d-5485-4af0-bd8e-eedd7bf08479.jpeg"
+ }
+ ],
+ "href": "/users/1326053/recents/67584",
+ "links": { "project": "1993", "workflow": "3760", "subject": "251783" },
+ "slug": "kieftrav/freehand-line-multiframe"
+ },
+ {
+ "id": "67540",
+ "created_at": "2024-02-20T20:17:19.138Z",
+ "updated_at": "2024-02-20T20:17:19.138Z",
+ "locations": [
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/a7495d3c-a589-4b13-9a55-43db82c31da2.jpeg"
+ }
+ ],
+ "href": "/users/1326053/recents/67540",
+ "links": { "project": "1928", "workflow": "3458", "subject": "135400" },
+ "slug": "goplayoutside3/test-bike-lane-uprising"
+ },
+ {
+ "id": "67539",
+ "created_at": "2024-02-20T20:17:16.167Z",
+ "updated_at": "2024-02-20T20:17:16.167Z",
+ "locations": [
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/88266d91-899d-404c-865c-f420693b7eeb.jpeg"
+ }
+ ],
+ "href": "/users/1326053/recents/67539",
+ "links": { "project": "1928", "workflow": "3458", "subject": "135401" },
+ "slug": "goplayoutside3/test-bike-lane-uprising"
+ },
+ {
+ "id": "67522",
+ "created_at": "2024-02-14T17:14:26.898Z",
+ "updated_at": "2024-02-14T17:14:26.898Z",
+ "locations": [
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/090c9b26-2225-4ae7-8e79-a87a076f8ae0.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/1f2d5479-4849-4793-a6b1-029b84bf8b9c.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/675a518c-ffe0-47d8-b5f3-fff7d7d920f1.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/024070aa-15d3-41ba-9448-92c0dfc00f20.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/cb376c02-5ce1-4a64-9b02-e705a919ad2c.jpeg"
+ }
+ ],
+ "href": "/users/1326053/recents/67522",
+ "links": { "project": "1993", "workflow": "3760", "subject": "251784" },
+ "slug": "kieftrav/freehand-line-multiframe"
+ },
+ {
+ "id": "67458",
+ "created_at": "2024-01-11T20:54:04.213Z",
+ "updated_at": "2024-01-11T20:54:04.213Z",
+ "locations": [
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/3814424e-901d-4900-9fb6-f224809f256b.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/ee7fc6e6-2b32-4f00-abf2-01d388627870.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/a0d95070-ac3b-4da9-84d1-b83a3ee70fdc.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/2b00a010-25fe-4f4b-ad29-8fc74093aba6.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/cae4c5fb-f0bf-4ae6-8713-7bbdfba03093.jpeg"
+ }
+ ],
+ "href": "/users/1326053/recents/67458",
+ "links": { "project": "2002", "workflow": "3782", "subject": "251779" },
+ "slug": "kieftrav/annotate-or-not"
+ },
+ {
+ "id": "67457",
+ "created_at": "2024-01-11T20:53:35.827Z",
+ "updated_at": "2024-01-11T20:53:35.827Z",
+ "locations": [
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/ba246527-dce2-4196-92ef-f08316ed5349.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/e32793a0-546a-457b-9b92-20669a2a1e60.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/4719532a-3933-49c9-859f-d866280dfcaa.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/c128e9b1-77cf-4096-af9c-29944f1f9866.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/ff27b5db-5c9a-4fec-9051-33c5a3f2f081.jpeg"
+ }
+ ],
+ "href": "/users/1326053/recents/67457",
+ "links": { "project": "2002", "workflow": "3782", "subject": "251780" },
+ "slug": "kieftrav/annotate-or-not"
+ },
+ {
+ "id": "67456",
+ "created_at": "2024-01-11T20:52:24.081Z",
+ "updated_at": "2024-01-11T20:52:24.081Z",
+ "locations": [
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/5cb88ea3-d4f0-4a3a-8a52-7a81ced9c796.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/d4d89ac9-9d4f-4298-b2ab-fceba6155720.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/f7ef0399-f063-4465-a7f9-e8f9366f3b52.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/ab49d29c-b1da-4c65-b5fb-70d467c829e9.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/40bd2112-a030-4954-a4c6-66fcba091e5b.jpeg"
+ }
+ ],
+ "href": "/users/1326053/recents/67456",
+ "links": { "project": "2002", "workflow": "3782", "subject": "251782" },
+ "slug": "kieftrav/annotate-or-not"
+ },
+ {
+ "id": "67455",
+ "created_at": "2024-01-11T20:35:05.950Z",
+ "updated_at": "2024-01-11T20:35:05.950Z",
+ "locations": [
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/b66fdbf9-f52f-4bb4-92f2-daa2cb62ef37.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/d0ed74cf-bf9e-4aee-a489-b558882c1892.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/77b7434e-c04f-42da-96f0-2fb92af19af1.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/2342d836-65a6-4e12-b49d-40471e495dec.jpeg"
+ },
+ {
+ "image/jpeg": "https://panoptes-uploads-staging.zooniverse.org/subject_location/fc2366e0-597d-4326-b04b-d84e0842a56c.jpeg"
+ }
+ ],
+ "href": "/users/1326053/recents/67455",
+ "links": { "project": "1993", "workflow": "3760", "subject": "251778" },
+ "slug": "kieftrav/freehand-line-multiframe"
+ }
+]
diff --git a/packages/lib-user/src/components/UserHome/components/SubjectCard/SubjectCard.js b/packages/lib-user/src/components/UserHome/components/SubjectCard/SubjectCard.js
new file mode 100644
index 0000000000..2c131ad333
--- /dev/null
+++ b/packages/lib-user/src/components/UserHome/components/SubjectCard/SubjectCard.js
@@ -0,0 +1,103 @@
+/* This component will eventually be moved to lib-react-components to be a universal card including
+ - Subject image or screenshot
+ - Subject id
+ - Metadata info button
+ - Favorite button
+ - Add to collections button
+ - Share button with link to Talk url
+*/
+
+/* The shape and styling of this component is similar to ProjectCard in lib-react-components */
+
+import styled from 'styled-components'
+import { Anchor, Box } from 'grommet'
+import { Media, SpacedText } from '@zooniverse/react-components'
+import { number, string } from 'prop-types'
+
+const StyledAnchor = styled(Anchor)`
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: none;
+ }
+`
+
+const StyledBox = styled(Box)`
+ overflow: hidden;
+ position: relative;
+`
+
+const Gradient = styled(Box)`
+ top: 0;
+ right: 0;
+ position: absolute;
+ background: linear-gradient(
+ to top,
+ rgba(0, 0, 0, 0.8) -30%,
+ rgba(0, 0, 0, 0.2) 30%,
+ transparent 100%
+ );
+ z-index: 2; // Must be in front of Grommet Video component's z-index of 1
+`
+
+const StyledSpacedText = styled(SpacedText)`
+ bottom: 1em;
+ left: 1em;
+ position: absolute;
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
+ z-index: 3;
+`
+
+function cardWidth(size) {
+ switch (size) {
+ case 'small':
+ return 157
+ case 'medium':
+ return 189
+ case 'large':
+ return 220
+ case 'xlarge':
+ return 252
+ default:
+ return 189
+ }
+}
+
+export default function SubjectCard({
+ size = 'medium',
+ mediaSrc = '',
+ projectSlug = '',
+ subjectID
+}) {
+ // to PFE
+ const href = `https://www.zooniverse.org/projects/${projectSlug}/talk/subjects/${subjectID}`
+
+ return (
+
+
+
+
+
+ {'Subject ' + subjectID}
+
+
+
+ )
+}
+
+SubjectCard.propTypes = {
+ size: string,
+ projectSlug: string,
+ mediaSrc: string,
+ subjectID: number.isRequired
+}
diff --git a/packages/lib-user/src/components/UserHome/components/SubjectCard/SubjectCard.spec.js b/packages/lib-user/src/components/UserHome/components/SubjectCard/SubjectCard.spec.js
new file mode 100644
index 0000000000..abd289a5fa
--- /dev/null
+++ b/packages/lib-user/src/components/UserHome/components/SubjectCard/SubjectCard.spec.js
@@ -0,0 +1,24 @@
+import { composeStory } from '@storybook/react'
+import { render, screen } from '@testing-library/react'
+
+import Meta, { Default } from './SubjectCard.stories.js'
+
+describe('UserHome > compoents > SubjectCard', function () {
+ const DefaultStory = composeStory(Default, Meta)
+
+ it('should show the subject id', function () {
+ render()
+
+ // grabbing the first element because each Story renders all four ProjectCard sizes
+ expect(
+ screen.getAllByText(`Subject ${Default.args.subjectID}`)[0]
+ ).to.be.ok()
+ })
+
+ it('should link to the subject Talk page', function () {
+ render()
+
+ const href = `https://www.zooniverse.org/projects/${Default.args.projectSlug}/talk/subjects/${Default.args.subjectID}`
+ expect(screen.getAllByRole('link', { href: href})[0]).to.be.ok()
+ })
+})
diff --git a/packages/lib-user/src/components/UserHome/components/SubjectCard/SubjectCard.stories.js b/packages/lib-user/src/components/UserHome/components/SubjectCard/SubjectCard.stories.js
new file mode 100644
index 0000000000..99d4e4db1a
--- /dev/null
+++ b/packages/lib-user/src/components/UserHome/components/SubjectCard/SubjectCard.stories.js
@@ -0,0 +1,75 @@
+import { Box } from 'grommet'
+import SubjectCard from './SubjectCard'
+
+export default {
+ title: 'Components / UserHome / SubjectCard',
+ component: SubjectCard
+}
+
+// .jpeg or .png
+export const Default = {
+ args: {
+ subjectID: 123,
+ mediaSrc: 'https://panoptes-uploads-staging.zooniverse.org/subject_location/34d154ca-1d8a-4c8a-ae29-96f58e0700f4.jpeg',
+ projectSlug: 'brooke/i-fancy-cats'
+ },
+ render: args => (
+
+
+
+
+
+
+ )
+}
+
+// .mp4
+export const Video = {
+ args: {
+ subjectID: 456,
+ mediaSrc: 'https://panoptes-uploads.zooniverse.org/subject_location/7da396d3-04f6-44b9-aef5-db4cac5dc172.mp4',
+ projectSlug: 'sophiemu/solar-jet-hunter'
+ },
+ render: args => (
+
+
+
+
+
+
+ )
+}
+
+// .txt
+export const Text = {
+ args: {
+ subjectID: 789,
+ mediaSrc: 'https://panoptes-uploads.zooniverse.org/subject_location/f5506d1c-a0e9-4aba-a418-6a6c46a7731a.txt',
+ projectSlug: 'markb-panoptes/digileap-testing-staging'
+ },
+ render: args => (
+
+
+
+
+
+
+ )
+}
+
+// .json
+export const Data = {
+ args: {
+ subjectID: 8364,
+ mediaSrc: 'https://panoptes-uploads.zooniverse.org/subject_location/bdc78b71-ce68-4e10-9ae7-0e643e215143.json',
+ projectSlug: 'cobalt-lensing/black-hole-hunters'
+ },
+ render: args => (
+
+
+
+
+
+
+ )
+}
diff --git a/packages/lib-user/src/components/UserHome/index.js b/packages/lib-user/src/components/UserHome/index.js
new file mode 100644
index 0000000000..6d68c1678e
--- /dev/null
+++ b/packages/lib-user/src/components/UserHome/index.js
@@ -0,0 +1 @@
+export { default } from './UserHome'
diff --git a/packages/lib-user/src/components/index.js b/packages/lib-user/src/components/index.js
index b58936f441..4d88f0384c 100644
--- a/packages/lib-user/src/components/index.js
+++ b/packages/lib-user/src/components/index.js
@@ -1,4 +1,5 @@
export { default as Contributors } from './Contributors'
export { default as GroupStats } from './GroupStats'
export { default as MyGroups } from './MyGroups'
+export { default as UserHome } from './UserHome'
export { default as UserStats } from './UserStats'
diff --git a/packages/lib-user/src/index.js b/packages/lib-user/src/index.js
index 96dab20304..2622143a4d 100644
--- a/packages/lib-user/src/index.js
+++ b/packages/lib-user/src/index.js
@@ -2,6 +2,7 @@
export { default as Contributors } from './components/Contributors'
export { default as GroupStats } from './components/GroupStats'
export { default as MyGroups } from './components/MyGroups'
+export { default as UserHome } from './components/UserHome'
export { default as UserStats } from './components/UserStats'
// components/shared