diff --git a/app/components/activity-photo-item.gjs b/app/components/activity-photo-item.gjs
new file mode 100644
index 0000000..44579f1
--- /dev/null
+++ b/app/components/activity-photo-item.gjs
@@ -0,0 +1,132 @@
+import Component from '@glimmer/component';
+import { on } from '@ember/modifier';
+import { fn } from '@ember/helper';
+import formatRelativeDate from '../helpers/format-relative-date';
+import { npubEncode } from 'applesauce-core/helpers/pointers';
+import Icon from './icon';
+
+export default class ActivityPhotoItem extends Component {
+ get item() {
+ return this.args.item;
+ }
+
+ get senderName() {
+ return this.item?.senderName;
+ }
+
+ get senderDisplayName() {
+ const name = this.senderName;
+ if (name) return name;
+ const pubkey = this.item?.senderPubkey;
+ if (!pubkey) return 'Someone';
+ try {
+ return `${npubEncode(pubkey).slice(0, 12)}…`;
+ } catch {
+ return `${pubkey.slice(0, 12)}…`;
+ }
+ }
+
+ get senderAvatar() {
+ return this.item?.senderAvatar;
+ }
+
+ get hasPhoto() {
+ return this.photos.length > 0;
+ }
+
+ get photos() {
+ const photos = this.item?.photos;
+ if (photos && photos.length > 0) return photos;
+ const single = this.item?.photo;
+ return single ? [single] : [];
+ }
+
+ get primaryPhoto() {
+ return this.photos[0];
+ }
+
+ get extraPhotoCount() {
+ return Math.max(0, this.photos.length - 1);
+ }
+
+ get photoCountText() {
+ const count = this.photos.length;
+ return count === 1 ? 'a photo' : `${count} photos`;
+ }
+
+ get photoThumbUrl() {
+ return this.primaryPhoto?.thumbUrl || this.primaryPhoto?.url;
+ }
+
+ get placeName() {
+ return this.item?.placeName;
+ }
+
+ get placeNameLoading() {
+ return this.item?.placeNameLoading;
+ }
+
+
+