kyd-shared-badge 0.3.69 → 0.3.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kyd-shared-badge",
3
- "version": "0.3.69",
3
+ "version": "0.3.70",
4
4
  "private": false,
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -23,6 +23,7 @@
23
23
  "@chatscope/chat-ui-kit-styles": "^1.4.0",
24
24
  "ai": "5.0.47",
25
25
  "i18n-iso-countries": "^7.14.0",
26
+ "lucide-react": "^0.545.0",
26
27
  "next-auth": "^4.24.11",
27
28
  "react-hot-toast": "^2.6.0",
28
29
  "react-icons": "^5.5.0",
@@ -3,6 +3,8 @@ import { useRouter } from 'next/navigation';
3
3
  import { ProviderIcon } from '../utils/provider';
4
4
  import { normalizeLinkedInInput } from './linkedin';
5
5
  import type { ConnectAccountsProps } from './types';
6
+ import { CheckCircle, Link2, LinkIcon, Unlink } from 'lucide-react';
7
+ import { Button, Input, Spinner, Card, CardHeader, CardContent, CardFooter } from '../ui';
6
8
 
7
9
  function byPriority(a: string, b: string) {
8
10
  const pr = (id: string) => (id === 'github' ? 0 : id === 'linkedin' ? 1 : 2);
@@ -128,90 +130,129 @@ export function ConnectAccounts(props: ConnectAccountsProps) {
128
130
 
129
131
  return (
130
132
  <div className={className}>
131
- {list.map((provider) => {
132
- const providerId = provider.id;
133
- const isConnected = connectedIds.has(providerId.toLowerCase());
134
- const needsReconnect = reconnectIds.has(providerId.toLowerCase());
135
- const isUrl = (provider.connectionType || 'url') === 'url' || provider.connectionType === 'link';
136
- const isOauth = provider.connectionType === 'oauth';
137
- return (
138
- <div key={providerId} className="group flex items-center justify-between p-2 rounded-lg transition-colors hover:bg-[var(--icon-button-secondary)]/10">
139
- <div className="flex items-center gap-3">
140
- <ProviderIcon name={providerId} className={`sm:size-7 size-5 ${provider.iconColor || 'text-gray-500'}`} />
141
- <span className="font-medium sm:text-base text-sm" style={{ color: 'var(--text-main)'}}>{provider.name}</span>
142
- {provider.beta ? (
143
- <span
144
- className="ml-2 inline-block rounded-full px-2 py-0.5 text-xs font-semibold bg-blue-100 text-blue-700 border border-blue-200"
145
- style={{ verticalAlign: 'middle', letterSpacing: '0.05em' }}
146
- >
147
- Beta
148
- </span>
149
- ) : null}
150
- </div>
133
+ {(() => {
134
+ const oauthList = list.filter(p => p.connectionType === 'oauth');
135
+ const urlList = list.filter(p => (p.connectionType || 'url') === 'url' || p.connectionType === 'link');
151
136
 
152
- {isConnected ? (
153
- <div className="relative flex items-center">
154
- <div className="flex items-center gap-2 transition-opacity group-hover:opacity-0" style={{ color: 'var(--success-green)'}}>
155
- {/* Check icon via CSS/emoji to avoid extra deps */}
156
- <span className="w-4 h-4 inline-block">✔</span>
157
- <span className="text-sm font-medium">Connected</span>
158
- </div>
159
- <div className="absolute right-0 opacity-0 transition-opacity group-hover:opacity-100">
160
- <button
161
- onClick={() => onDisconnect(providerId)}
162
- className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm text-red-600 hover:text-red-700"
137
+ const Row = (provider: typeof list[number]) => {
138
+ const providerId = provider.id;
139
+ const isConnected = connectedIds.has(providerId.toLowerCase());
140
+ const needsReconnect = reconnectIds.has(providerId.toLowerCase());
141
+ const isUrl = (provider.connectionType || 'url') === 'url' || provider.connectionType === 'link';
142
+ const isOauth = provider.connectionType === 'oauth';
143
+ return (
144
+ <div key={providerId} className="group flex items-center justify-between p-2 rounded-lg transition-colors hover:bg-[var(--icon-button-secondary)]/10 transform transition-transform hover:-translate-y-px">
145
+ <div className="flex items-center gap-3">
146
+ <ProviderIcon name={providerId} className={`sm:size-7 size-5 ${provider.iconColor || 'text-gray-500'}`} />
147
+ <span className="font-medium sm:text-base text-sm" style={{ color: 'var(--text-main)'}}>{provider.name}</span>
148
+ {provider.beta ? (
149
+ <span
150
+ className="ml-2 inline-block rounded-full px-2 py-0.5 text-xs font-semibold bg-blue-100 text-blue-700 border border-blue-200"
151
+ style={{ verticalAlign: 'middle', letterSpacing: '0.05em' }}
163
152
  >
164
- <span className="w-3 h-3 inline-block">✕</span>
165
- <span>Disconnect</span>
166
- </button>
167
- </div>
153
+ Beta
154
+ </span>
155
+ ) : null}
168
156
  </div>
169
- ) : (
170
- <div className="flex items-center gap-2">
171
- {selectedProviderId === providerId && isUrl ? (
172
- <form onSubmit={(e) => { e.preventDefault(); onSubmitLink(providerId); }} className="flex items-center gap-2">
173
- <div className="relative">
174
- <input
175
- type="url"
176
- value={linkUrl}
177
- onChange={(e) => setLinkUrl(e.target.value)}
178
- placeholder={provider.placeholder || 'https://example.com/your-profile'}
179
- required
180
- className="w-72 border bg-transparent p-2 text-sm rounded"
181
- style={{ color: 'var(--text-main)', borderColor: 'var(--icon-button-secondary)'}}
182
- onPaste={providerId === 'linkedin' ? (e) => { const text = e.clipboardData.getData('text'); setLinkUrl(normalizeLinkedInInput(text)); e.preventDefault(); } : undefined}
183
- onBlur={providerId === 'linkedin' ? (() => setLinkUrl(normalizeLinkedInInput(linkUrl))) : undefined}
184
- />
185
- </div>
186
- <button type="submit" disabled={isSubmitting} className="px-3 py-2 rounded bg-[var(--icon-accent)] text-white">
187
- {isSubmitting ? 'Connecting…' : 'Connect'}
188
- </button>
189
- </form>
190
- ) : (
191
- <>
192
- <button
193
- onClick={() => (isOauth ? onOAuth(providerId) : setSelectedProviderId(providerId))}
194
- className="bg-[var(--icon-button-secondary)] text-[var(--text-main)] hover:bg-[var(--icon-accent)] hover:text-white border-0 sm:px-4 px-3 py-1 sm:py-2 rounded"
157
+
158
+ {isConnected ? (
159
+ <div className="relative flex items-center">
160
+ <div className="flex items-center gap-2 transition-opacity group-hover:opacity-0" style={{ color: 'var(--success-green)'}}>
161
+ {/* CheckCircle icon (inline SVG) */}
162
+ <CheckCircle className="size-3 sm:size-4" />
163
+ <span className="text-sm font-medium">Connected</span>
164
+ </div>
165
+ <div className="absolute right-0 opacity-0 transition-opacity group-hover:opacity-100">
166
+ <Button
167
+ onClick={() => onDisconnect(providerId)}
168
+ className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm text-red-600 hover:text-red-700 hover:underline"
195
169
  >
196
- <span className="sm:text-base text-sm">Connect</span>
197
- </button>
198
- {needsReconnect && (
199
- <button
200
- onClick={() => onDisconnect(providerId)}
201
- className="inline-flex items-center justify-center gap-1.5 px-4 py-2 text-sm rounded border"
202
- style={{ color: 'var(--text-main)', borderColor: 'var(--icon-button-secondary)'}}
170
+ {/* Unlink icon (inline SVG) */}
171
+ <Unlink className="size-3 sm:size-4" />
172
+ <span>Disconnect</span>
173
+ </Button>
174
+ </div>
175
+ </div>
176
+ ) : (
177
+ <div className="flex items-center gap-2">
178
+ {selectedProviderId === providerId && isUrl ? (
179
+ <form onSubmit={(e) => { e.preventDefault(); onSubmitLink(providerId); }} className="flex items-center gap-2">
180
+ <div className="relative">
181
+ <Input
182
+ type="url"
183
+ value={linkUrl}
184
+ onChange={(e) => setLinkUrl(e.target.value)}
185
+ placeholder={provider.placeholder || 'https://example.com/your-profile'}
186
+ required
187
+ className="w-72 border bg-transparent p-2 text-sm rounded"
188
+ style={{ color: 'var(--text-main)', borderColor: 'var(--icon-button-secondary)'}}
189
+ onPaste={providerId === 'linkedin' ? (e) => { const text = e.clipboardData.getData('text'); setLinkUrl(normalizeLinkedInInput(text)); e.preventDefault(); } : undefined}
190
+ onBlur={providerId === 'linkedin' ? (() => setLinkUrl(normalizeLinkedInInput(linkUrl))) : undefined}
191
+ />
192
+ </div>
193
+ <Button type="submit" disabled={isSubmitting} className="px-3 py-2 rounded bg-[var(--icon-accent)] text-white">
194
+ {isSubmitting ? 'Connecting…' : 'Connect'}
195
+ </Button>
196
+ </form>
197
+ ) : (
198
+ <>
199
+ <Button
200
+ onClick={() => (isOauth ? onOAuth(providerId) : setSelectedProviderId(providerId))}
201
+ className="bg-[var(--icon-button-secondary)] text-[var(--text-main)] hover:bg-[var(--icon-accent)] hover:text-white border-0 sm:px-4 px-3 py-1 sm:py-2 rounded-lg flex items-center gap-2"
203
202
  >
204
- <span className="w-3 h-3 inline-block">✕</span>
205
- <span>Remove</span>
206
- </button>
207
- )}
208
- </>
209
- )}
210
- </div>
211
- )}
212
- </div>
203
+ {/* OAuth/URL leading icon */}
204
+ {isOauth ? (
205
+ <Link2 className="size-3 sm:size-4" />
206
+ ) : (
207
+ <LinkIcon className="size-3 sm:size-4" />
208
+ )}
209
+ <span className="sm:text-base text-sm">Connect</span>
210
+ </Button>
211
+ {needsReconnect && (
212
+ <Button
213
+ onClick={() => onDisconnect(providerId)}
214
+ className="inline-flex items-center justify-center gap-1.5 px-4 py-2 text-sm rounded border"
215
+ style={{ color: 'var(--text-main)', borderColor: 'var(--icon-button-secondary)'}}
216
+ >
217
+ {/* Unlink icon (inline SVG) */}
218
+ <Unlink className="size-3 sm:size-4" />
219
+ <span>Remove</span>
220
+ </Button>
221
+ )}
222
+ </>
223
+ )}
224
+ </div>
225
+ )}
226
+ </div>
227
+ );
228
+ };
229
+
230
+ return (
231
+ <>
232
+ {oauthList.length ? (
233
+ <>
234
+ <div className="flex items-center my-2">
235
+ <div className="flex-1 border-t" style={{ borderColor: 'var(--icon-button-secondary)', opacity: 0.3 }} />
236
+ <span className="mx-3 text-[10px] sm:text-xs uppercase tracking-wide" style={{ color: 'var(--text-secondary)'}}>OAuth</span>
237
+ <div className="flex-1 border-t" style={{ borderColor: 'var(--icon-button-secondary)', opacity: 0.3 }} />
238
+ </div>
239
+ {oauthList.map(p => Row(p))}
240
+ </>
241
+ ) : null}
242
+
243
+ {urlList.length ? (
244
+ <>
245
+ <div className="flex items-center my-2">
246
+ <div className="flex-1 border-t" style={{ borderColor: 'var(--icon-button-secondary)', opacity: 0.3 }} />
247
+ <span className="mx-3 text-[10px] sm:text-xs uppercase tracking-wide" style={{ color: 'var(--text-secondary)'}}>Public Urls</span>
248
+ <div className="flex-1 border-t" style={{ borderColor: 'var(--icon-button-secondary)', opacity: 0.3 }} />
249
+ </div>
250
+ {urlList.map(p => Row(p))}
251
+ </>
252
+ ) : null}
253
+ </>
213
254
  );
214
- })}
255
+ })()}
215
256
  </div>
216
257
  );
217
258
  }
@@ -0,0 +1,57 @@
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default:
13
+ "bg-primary text-primary-foreground shadow hover:bg-primary/90",
14
+ destructive:
15
+ "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16
+ outline:
17
+ "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
18
+ secondary:
19
+ "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20
+ ghost: "hover:bg-accent hover:text-accent-foreground",
21
+ link: "text-primary underline-offset-4 hover:underline",
22
+ },
23
+ size: {
24
+ default: "h-9 px-4 py-2",
25
+ sm: "h-8 rounded-md px-3 text-xs",
26
+ lg: "h-10 rounded-md px-8",
27
+ icon: "h-9 w-9",
28
+ },
29
+ },
30
+ defaultVariants: {
31
+ variant: "default",
32
+ size: "default",
33
+ },
34
+ }
35
+ )
36
+
37
+ export interface ButtonProps
38
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
39
+ VariantProps<typeof buttonVariants> {
40
+ asChild?: boolean
41
+ }
42
+
43
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
44
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
45
+ const Comp = asChild ? Slot : "button"
46
+ return (
47
+ <Comp
48
+ className={cn(buttonVariants({ variant, size, className }))}
49
+ ref={ref}
50
+ {...props}
51
+ />
52
+ )
53
+ }
54
+ )
55
+ Button.displayName = "Button"
56
+
57
+ export { Button, buttonVariants }
@@ -0,0 +1,76 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const Card = React.forwardRef<
6
+ HTMLDivElement,
7
+ React.HTMLAttributes<HTMLDivElement>
8
+ >(({ className, ...props }, ref) => (
9
+ <div
10
+ ref={ref}
11
+ className={cn(
12
+ "rounded-lg border bg-card text-card-foreground shadow",
13
+ className
14
+ )}
15
+ {...props}
16
+ />
17
+ ))
18
+ Card.displayName = "Card"
19
+
20
+ const CardHeader = React.forwardRef<
21
+ HTMLDivElement,
22
+ React.HTMLAttributes<HTMLDivElement>
23
+ >(({ className, ...props }, ref) => (
24
+ <div
25
+ ref={ref}
26
+ className={cn("flex flex-col sm:p-4 p-4", className)}
27
+ {...props}
28
+ />
29
+ ))
30
+ CardHeader.displayName = "CardHeader"
31
+
32
+ const CardTitle = React.forwardRef<
33
+ HTMLDivElement,
34
+ React.HTMLAttributes<HTMLDivElement>
35
+ >(({ className, ...props }, ref) => (
36
+ <div
37
+ ref={ref}
38
+ className={cn("font-semibold leading-none tracking-tight", className)}
39
+ {...props}
40
+ />
41
+ ))
42
+ CardTitle.displayName = "CardTitle"
43
+
44
+ const CardDescription = React.forwardRef<
45
+ HTMLDivElement,
46
+ React.HTMLAttributes<HTMLDivElement>
47
+ >(({ className, ...props }, ref) => (
48
+ <div
49
+ ref={ref}
50
+ className={cn("text-sm text-muted-foreground", className)}
51
+ {...props}
52
+ />
53
+ ))
54
+ CardDescription.displayName = "CardDescription"
55
+
56
+ const CardContent = React.forwardRef<
57
+ HTMLDivElement,
58
+ React.HTMLAttributes<HTMLDivElement>
59
+ >(({ className, ...props }, ref) => (
60
+ <div ref={ref} className={cn("sm:pt-2 sm:pb-4 sm:px-4 pb-3 px-4 pt-0", className)} {...props} />
61
+ ))
62
+ CardContent.displayName = "CardContent"
63
+
64
+ const CardFooter = React.forwardRef<
65
+ HTMLDivElement,
66
+ React.HTMLAttributes<HTMLDivElement>
67
+ >(({ className, ...props }, ref) => (
68
+ <div
69
+ ref={ref}
70
+ className={cn("flex items-center sm:p-6 p-2 pt-0", className)}
71
+ {...props}
72
+ />
73
+ ))
74
+ CardFooter.displayName = "CardFooter"
75
+
76
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
@@ -0,0 +1,4 @@
1
+ export * from './button';
2
+ export * from './card';
3
+ export * from './input';
4
+ export * from './spinner';
@@ -0,0 +1,22 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
6
+ ({ className, type, ...props }, ref) => {
7
+ return (
8
+ <input
9
+ type={type}
10
+ className={cn(
11
+ "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm text-xs",
12
+ className
13
+ )}
14
+ ref={ref}
15
+ {...props}
16
+ />
17
+ )
18
+ }
19
+ )
20
+ Input.displayName = "Input"
21
+
22
+ export { Input }
@@ -0,0 +1,12 @@
1
+ 'use client'
2
+ import React from 'react'
3
+
4
+ export function Spinner() {
5
+ return (
6
+ <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-[var(--icon-accent-hover)]"></div>
7
+ )
8
+ }
9
+
10
+ export default Spinner
11
+
12
+