Skip to content

Commit 0664072

Browse files
committed
feat: implement user card
1 parent 1523c00 commit 0664072

File tree

3 files changed

+158
-27
lines changed

3 files changed

+158
-27
lines changed
Lines changed: 1 addition & 0 deletions
Loading

packages/agent-webapp/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
Contoso Burgers AI Agent
1515
<div class="spacer"></div>
1616
<azc-auth type="guard">
17-
<a href="">Member card</a>
17+
<azc-user-card></azc-user-card>
1818
</azc-auth>
1919
<azc-auth type="status"></azc-auth>
2020
</nav>

packages/agent-webapp/src/components/user-card.ts

Lines changed: 156 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { LitElement, css, html } from "lit";
1+
import { LitElement, css, html, nothing } from "lit";
22
import { unsafeSVG } from "lit/directives/unsafe-svg.js";
33
import { customElement, state } from "lit/decorators.js";
44
import { getUserInfo } from "../auth.service.js";
55
import copySvg from "../../assets/icons/copy.svg?raw";
66
import burgerOutlineSvg from "../../assets/icons/burger-outline.svg?raw";
7+
import cardSvg from "../../assets/icons/card.svg?raw";
78

89
export const apiBaseUrl: string =
910
import.meta.env.VITE_REGISTRATION_API_URL || "";
@@ -14,12 +15,50 @@ export class UserCard extends LitElement {
1415
@state() protected isLoading = false;
1516
@state() protected hasError = false;
1617
@state() protected username: string = "";
18+
@state() protected isOpen = false;
1719

1820
constructor() {
1921
super();
2022
this.getUserId();
2123
}
2224

25+
openModal() {
26+
this.isOpen = true;
27+
document.body.style.overflow = 'hidden';
28+
}
29+
30+
closeModal() {
31+
this.isOpen = false;
32+
document.body.style.overflow = '';
33+
}
34+
35+
protected handleNavClick = () => {
36+
this.openModal();
37+
};
38+
39+
protected handleOverlayClick = (e: Event) => {
40+
if (e.target === e.currentTarget) {
41+
this.closeModal();
42+
}
43+
};
44+
45+
protected handleEscapeKey = (e: KeyboardEvent) => {
46+
if (e.key === 'Escape') {
47+
this.closeModal();
48+
}
49+
};
50+
51+
override connectedCallback() {
52+
super.connectedCallback();
53+
document.addEventListener('keydown', this.handleEscapeKey);
54+
}
55+
56+
override disconnectedCallback() {
57+
super.disconnectedCallback();
58+
document.removeEventListener('keydown', this.handleEscapeKey);
59+
document.body.style.overflow = '';
60+
}
61+
2362
protected renderLoading = () => html`<p>Loading...</p>`;
2463

2564
protected copyUserIdToClipboard = async () => {
@@ -49,9 +88,10 @@ export class UserCard extends LitElement {
4988

5089
protected renderRegistrationCard = () => html`
5190
<div class="card card-shine">
52-
<span class="slice">${unsafeSVG(burgerOutlineSvg)}</span>
91+
<span class="burger">${unsafeSVG(burgerOutlineSvg)}</span>
5392
<div class="card-content">
54-
<h1>Contoso Burgers Membership</h1>
93+
<h1>Contoso Burgers</h1>
94+
<h2>Membership Card</h2>
5595
<p>Card attributed to:</p>
5696
<div><pre>${this.username}</pre></div>
5797
<p>Unique user ID:</p>
@@ -96,59 +136,150 @@ export class UserCard extends LitElement {
96136
}
97137
};
98138

139+
protected renderNavLink = () => html`
140+
<button @click="${this.handleNavClick}" class="member-card-link">
141+
<span class="card-icon">${unsafeSVG(cardSvg)}</span>
142+
Member card
143+
</button>
144+
`;
145+
146+
protected renderModal = () => html`
147+
<div class="modal-overlay" @click="${this.handleOverlayClick}">
148+
<div class="modal-content">
149+
<button class="close-button" @click="${this.closeModal}" aria-label="Close modal">×</button>
150+
${this.isLoading
151+
? this.renderLoading()
152+
: !this.username
153+
? this.renderError()
154+
: this.renderRegistrationCard()}
155+
</div>
156+
</div>
157+
`;
158+
99159
protected override render() {
100-
return this.isLoading
101-
? this.renderLoading()
102-
: !this.username
103-
? this.renderError()
104-
: this.renderRegistrationCard();
160+
return html`
161+
${this.renderNavLink()}
162+
${this.isOpen ? this.renderModal() : nothing}
163+
`;
105164
}
106165

107166
static override styles = css`
108167
:host {
109-
max-width: 1280px;
110-
margin: 0 auto;
168+
--reg-primary: linear-gradient(135deg, #de471d 0%, #ff6b3d 100%);
169+
--reg-border-radius: 16px;
170+
}
171+
172+
.member-card-link {
173+
background: none;
174+
border: none;
175+
color: #fff;
176+
text-decoration: none;
177+
padding: 0.5rem 1rem;
178+
border-radius: 4px;
179+
transition: background 0.2s;
180+
display: flex;
181+
align-items: center;
182+
gap: 0.5rem;
183+
cursor: pointer;
184+
font-family: inherit;
185+
font-size: inherit;
186+
}
187+
188+
.member-card-link:hover {
189+
background: rgba(255, 255, 255, 0.1);
190+
}
191+
192+
.card-icon {
193+
display: inline-block;
194+
fill: currentColor;
195+
}
196+
197+
.modal-overlay {
198+
position: fixed;
199+
top: 0;
200+
left: 0;
201+
right: 0;
202+
bottom: 0;
203+
background-color: rgba(0, 0, 0, 0.7);
204+
display: flex;
205+
align-items: center;
206+
justify-content: center;
207+
z-index: 1000;
111208
padding: 2rem;
112-
text-align: center;
209+
box-sizing: border-box;
210+
}
211+
212+
.modal-content {
213+
position: relative;
214+
max-width: 600px;
215+
width: 100%;
216+
max-height: 90vh;
217+
background: var(--reg-primary);
218+
border-radius: var(--reg-border-radius, 16px);
219+
}
220+
221+
.close-button {
222+
position: absolute;
223+
top: 1rem;
224+
right: 1rem;
225+
background: rgba(255, 255, 255, 0.2);
226+
border: none;
227+
border-radius: 50%;
228+
width: 2.5rem;
229+
height: 2.5rem;
230+
font-size: 1.5rem;
231+
font-weight: bold;
232+
cursor: pointer;
233+
display: flex;
234+
align-items: center;
235+
justify-content: center;
236+
z-index: 1001;
237+
color: #fff;
238+
transition: background 0.2s;
113239
}
240+
241+
.close-button:hover {
242+
background: rgba(255, 255, 255, .4);
243+
}
244+
114245
svg {
115246
fill: currentColor;
116247
width: 100%;
117248
}
118249
h1 {
119-
font-family: "Sofia Sans Condensed", sans-serif;
120-
font-size: 2.5em;
250+
font-size: 2em;
121251
color: #fff;
252+
margin: 0;
253+
font-weight: 600;
254+
text-transform: uppercase;
122255
}
123-
h1, p, pre, .warning {
256+
h1, h2 {
257+
font-family: "Sofia Sans Condensed", sans-serif;
258+
}
259+
h1, h2, pre, .warning {
124260
text-shadow: 0 1px 0px rgba(0, 0, 0, 0.5);
125261
}
126262
.card {
127263
position: relative;
128-
background-color: var(--reg-primary);
264+
background: var(--reg-primary);
129265
border-radius: var(--reg-border-radius);
130266
padding: 2rem;
131-
margin-bottom: 2rem;
132267
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2),
133268
-1px -1px 1px rgba(255, 255, 255, 0.3),
134269
2px 4px 8px rgba(0, 0, 0, 0.4);
135270
font-family: "Sofia Sans Condensed", sans-serif;
136271
text-align: left;
137-
overflow: hidden;
138272
width: 100%;
139273
box-sizing: border-box;
140274
color: #fff;
141275
font-size: 1.2rem;
142276
143-
h1 {
144-
font-size: 2.5rem;
145-
margin: 0;
146-
font-weight: 600;
147-
text-transform: uppercase;
148-
}
149277
h2 {
278+
font-size: 1em;
150279
margin: 0;
151280
font-weight: 600;
281+
font-style: italic;
282+
text-transform: uppercase;
152283
}
153284
pre {
154285
font-size: 1.5rem;
@@ -164,7 +295,6 @@ export class UserCard extends LitElement {
164295
.card-shine {
165296
--shine-deg: 45deg;
166297
position: relative;
167-
overflow: hidden;
168298
background-repeat: no-repeat;
169299
background-position: 0% 0, 0 0;
170300
background-image: linear-gradient(
@@ -182,7 +312,7 @@ export class UserCard extends LitElement {
182312
.card-shine:hover {
183313
background-position: 90% 0, 0 0;
184314
}
185-
.slice {
315+
.burger {
186316
z-index: 1;
187317
width: 10rem;
188318
height: 10rem;
@@ -191,7 +321,7 @@ export class UserCard extends LitElement {
191321
opacity: 0.4;
192322
position: absolute;
193323
right: -1rem;
194-
top: 2rem;
324+
top: 3.5rem;
195325
pointer-events: none;
196326
}
197327
.user-id-row {

0 commit comments

Comments
 (0)