diff --git a/app/components/activity-photo-item.gjs b/app/components/activity-photo-item.gjs
new file mode 100644
index 0000000..dc96ae6
--- /dev/null
+++ b/app/components/activity-photo-item.gjs
@@ -0,0 +1,134 @@
+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;
+ }
+
+
+
+
+
+
+}
diff --git a/app/components/activity-timeline.gjs b/app/components/activity-timeline.gjs
index 2cc6985..ebc23bc 100644
--- a/app/components/activity-timeline.gjs
+++ b/app/components/activity-timeline.gjs
@@ -4,15 +4,78 @@ import { tracked } from '@glimmer/tracking';
import { on } from '@ember/modifier';
import Icon from './icon';
import ActivityZapItem from './activity-zap-item';
+import ActivityPhotoItem from './activity-photo-item';
import Modal from './modal';
import NostrConnect from './nostr-connect';
import not from 'ember-truth-helpers/helpers/not';
import eq from 'ember-truth-helpers/helpers/eq';
import restoreScroll from '../modifiers/restore-scroll';
+import { ActivityEntry } from '../utils/activity';
export default class ActivityTimelineComponent extends Component {
@tracked isNostrConnectModalOpen = false;
+ get dummyPhotoEntry() {
+ const entry = new ActivityEntry({
+ photoEventId: 'dummy-photo-event-id',
+ photos: [
+ {
+ thumbUrl: 'https://picsum.photos/seed/marco-photo-1/200/200',
+ url: 'https://picsum.photos/seed/marco-photo-1/800/800',
+ placeIdentifier: 'osm:node:123456789',
+ },
+ ],
+ placeIdentifier: 'osm:node:123456789',
+ senderPubkey: 'dummy-sender-pubkey',
+ amountSats: 0,
+ message: null,
+ createdAt: Math.floor(Date.now() / 1000) - 3600,
+ });
+ entry.type = 'photo';
+ entry.senderName = 'Alice Mapper';
+ entry.senderAvatar =
+ 'https://api.dicebear.com/7.x/avataaars/svg?seed=alice';
+ entry.placeName = 'Central Park Café';
+ entry.placeNameLoading = false;
+ entry.senderProfileLoading = false;
+ return entry;
+ }
+
+ get dummyMultiPhotoEntry() {
+ const entry = new ActivityEntry({
+ photoEventId: 'dummy-multi-photo-event-id',
+ photos: [
+ {
+ thumbUrl: 'https://picsum.photos/seed/marco-multi-1/200/200',
+ url: 'https://picsum.photos/seed/marco-multi-1/800/800',
+ placeIdentifier: 'osm:node:987654321',
+ },
+ {
+ thumbUrl: 'https://picsum.photos/seed/marco-multi-2/200/200',
+ url: 'https://picsum.photos/seed/marco-multi-2/800/800',
+ placeIdentifier: 'osm:node:987654321',
+ },
+ {
+ thumbUrl: 'https://picsum.photos/seed/marco-multi-3/200/200',
+ url: 'https://picsum.photos/seed/marco-multi-3/800/800',
+ placeIdentifier: 'osm:node:987654321',
+ },
+ ],
+ placeIdentifier: 'osm:node:987654321',
+ senderPubkey: 'dummy-multi-sender-pubkey',
+ amountSats: 0,
+ message: null,
+ createdAt: Math.floor(Date.now() / 1000) - 7200,
+ });
+ entry.type = 'photo';
+ entry.senderName = 'Bob Cartographer';
+ entry.senderAvatar = 'https://api.dicebear.com/7.x/avataaars/svg?seed=bob';
+ entry.placeName = 'Riverside Lookout';
+ entry.placeNameLoading = false;
+ entry.senderProfileLoading = false;
+ return entry;
+ }
+
@action
openNostrConnectModal(event) {
event.preventDefault();
@@ -69,8 +132,18 @@ export default class ActivityTimelineComponent extends Component {
{{#each @items as |item|}}
{{#if (eq item.type "zap")}}
+ {{else if (eq item.type "photo")}}
+
{{/if}}
{{/each}}
+
+
{{/if}}
diff --git a/app/components/activity-zap-item.gjs b/app/components/activity-zap-item.gjs
index e404bf5..d58c8ae 100644
--- a/app/components/activity-zap-item.gjs
+++ b/app/components/activity-zap-item.gjs
@@ -50,40 +50,40 @@ export default class ActivityZapItem extends Component {