Skip to content

Commit 989e32c

Browse files
committed
Merge remote-tracking branch 'origin/master' into offline-sync
2 parents f266c96 + b9b9956 commit 989e32c

File tree

10 files changed

+128
-43
lines changed

10 files changed

+128
-43
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ variables.vars
5353
public/icons
5454
public/favicon.ico
5555
public/logo_*.svg
56+
public/logo_*.png
5657
public/manifest.json
5758
public/robots.txt
5859
public/sitemap.xml

src/components/Credentials/AddCredentialCard.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const AddCredentialCard = ({ onClick }) => {
1010
<button
1111
id="add-credential-card"
1212
onClick={onClick}
13-
className="step-1 relative w-full aspect-[1.6] rounded-xl overflow-hidden shadow-md hover:shadow-xl transition-all bg-linear-to-br from-lm-gray-600 via-lm-gray-700 to-lm-gray-800 text-lm-gray-100 dark:from-dm-gray-600 dark:via-dm-gray-700 dark:to-dm-gray-800"
13+
className="step-1 relative w-full aspect-[1.6] rounded-xl overflow-hidden cursor-pointer shadow-md hover:shadow-xl transition-all bg-linear-to-br from-lm-gray-600 via-lm-gray-700 to-lm-gray-800 text-lm-gray-100 dark:from-dm-gray-600 dark:via-dm-gray-700 dark:to-dm-gray-800"
1414
>
1515
{/* Decorative background layers */}
1616
<div className="absolute inset-0 bg-white/10 backdrop-blur-xs z-0" />

src/components/Credentials/CredentialTabs.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const CredentialTabs = ({ tabs, activeTab, onTabChange }) => {
77
<button
88
id={`credential-tab-${index}`}
99
key={index}
10-
className={`py-2 px-4 ${activeTab === index ? 'bg-lm-gray-500 dark:bg-dm-gray-500 text-lm-gray-900 dark:text-dm-gray-100 rounded-t-lg' : 'text-lm-gray-900 dark:text-dm-gray-100'}`}
10+
className={`py-2 px-4 ${activeTab === index ? 'bg-lm-gray-500 dark:bg-dm-gray-500 text-lm-gray-900 dark:text-dm-gray-100 rounded-t-lg' : 'text-lm-gray-900 dark:text-dm-gray-100 cursor-pointer'}`}
1111
onClick={() => onTabChange(index)}
1212
>
1313
{tab.label}

src/components/Logo/Logo.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { BRANDING } from '@/config';
12
import React, { useEffect, useState } from 'react';
23
import { useTranslation } from 'react-i18next';
34

@@ -11,8 +12,8 @@ interface LogoProps {
1112

1213
function getLogoUrl(type: string): string {
1314
return type === 'light' || type === ''
14-
? '/logo_light.svg'
15-
: '/logo_dark.svg';
15+
? BRANDING.LOGO_LIGHT
16+
: BRANDING.LOGO_DARK;
1617
}
1718

1819
const Logo: React.FC<LogoProps> = ({
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { createLucideIcon } from "lucide-react";
2+
3+
export const UsbStickDotIcon = createLucideIcon("usb-stick-dot", [
4+
[
5+
"path",
6+
{
7+
"d": "M18 8v10a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V8z",
8+
"key": "1pvj0n"
9+
}
10+
],
11+
[
12+
"path",
13+
{
14+
"d": "M8 7V2h8v5",
15+
"key": "payjgv"
16+
}
17+
],
18+
[
19+
"circle",
20+
{
21+
"cx": "12",
22+
"cy": "15",
23+
"r": "1",
24+
"fill": "currentColor",
25+
"key": "3me149"
26+
}
27+
]
28+
]);

src/config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,8 @@ export const OPENID4VCI_TRANSACTION_ID_POLLING_INTERVAL_IN_SECONDS = import.meta
2626
export const OPENID4VCI_TRANSACTION_ID_LIFETIME_IN_SECONDS = import.meta.env.VITE_OPENID4VCI_TRANSACTION_ID_LIFETIME_IN_SECONDS && !isNaN(parseInt(import.meta.env.VITE_OPENID4VCI_TRANSACTION_ID_LIFETIME_IN_SECONDS)) ? parseInt(import.meta.env.VITE_OPENID4VCI_TRANSACTION_ID_LIFETIME_IN_SECONDS) : 2592000;
2727
export const OHTTP_KEY_CONFIG = import.meta.env.VITE_OHTTP_KEY_CONFIG;
2828
export const OHTTP_RELAY = import.meta.env.VITE_OHTTP_RELAY;
29+
30+
export const BRANDING = {
31+
LOGO_LIGHT: import.meta.env.BRANDING_LOGO_LIGHT,
32+
LOGO_DARK: import.meta.env.BRANDING_LOGO_DARK,
33+
}

src/pages/Login/Login.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import checkForUpdates from '../../offlineUpdateSW';
1919
import ConnectionStatusIcon from '../../components/Layout/Navigation/ConnectionStatusIcon';
2020

2121
import useScreenType from '@/hooks/useScreenType';
22-
import { CircleQuestionMark, Eye, EyeOff, Info, Key, Lock, LockKeyholeOpen, Smartphone, User, UserLock, X } from 'lucide-react';
23-
22+
import { CircleQuestionMark, Eye, EyeOff, FingerprintIcon, Info, Lock, LockKeyholeOpen,SmartphoneNfcIcon, User, UserLock, X } from 'lucide-react';
23+
import { UsbStickDotIcon } from '@/components/Shared/CustomIcons';
2424

2525
const FormInputRow = ({
2626
IconComponent,
@@ -556,9 +556,9 @@ const WebauthnSignupLogin = ({
556556

557557
{!isLoginCache && (
558558
[
559-
{ hint: "client-device", btnLabel: t('common.platformPasskey'), Icon: UserLock, variant: coerce<Variant>("primary"), helpText: "Fastest option, recommended" },
560-
{ hint: "security-key", btnLabel: t('common.externalPasskey'), Icon: Key, variant: coerce<Variant>("outline"), helpText: "Use a USB or hardware security key" },
561-
{ hint: "hybrid", btnLabel: t('common.hybridPasskey'), Icon: Smartphone, variant: coerce<Variant>("outline"), helpText: "Scan QR or link mobile device" },
559+
{ hint: "client-device", btnLabel: t('common.platformPasskey'), Icon: FingerprintIcon, variant: coerce<Variant>("primary"), helpText: "Fastest option, recommended" },
560+
{ hint: "security-key", btnLabel: t('common.externalPasskey'), Icon: UsbStickDotIcon, variant: coerce<Variant>("outline"), helpText: "Use a USB or hardware security key" },
561+
{ hint: "hybrid", btnLabel: t('common.hybridPasskey'), Icon: SmartphoneNfcIcon, variant: coerce<Variant>("outline"), helpText: "Scan QR or link mobile device" },
562562
].map(({ Icon, hint, btnLabel, variant, helpText }) => (
563563
<div key={hint} className='mt-2 relative w-full flex flex-col justify-center'>
564564
<Button

src/pages/Settings/Settings.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import Button from '../../components/Buttons/Button';
1919
import { H1, H2, H3 } from '../../components/Shared/Heading';
2020
import PageDescription from '../../components/Shared/PageDescription';
2121
import LanguageSelector from '../../components/LanguageSelector/LanguageSelector';
22-
import { Bell, ChevronDown, Edit, Key, Laptop, Lock, LockOpen, Moon, RefreshCcw, Smartphone, Sun, Trash2, UserLock } from 'lucide-react';
22+
import { Bell, ChevronDown, Edit, FingerprintIcon, Laptop, Lock, LockOpen, Moon, RefreshCcw, Smartphone, SmartphoneNfcIcon, Sun, Trash2 } from 'lucide-react';
23+
import { UsbStickDotIcon } from '@/components/Shared/CustomIcons';
2324

2425
function useWebauthnCredentialNickname(credential: WebauthnCredential): string {
2526
const { t } = useTranslation();
@@ -213,9 +214,9 @@ const WebauthnRegistation = ({
213214
<span className="grow">{t('pageSettings.addPasskey')}</span>
214215
{
215216
[
216-
{ hint: "client-device", btnLabel: t('common.platformPasskey'), Icon: UserLock },
217-
{ hint: "security-key", btnLabel: t('common.externalPasskey'), Icon: Key },
218-
{ hint: "hybrid", btnLabel: t('common.hybridPasskey'), Icon: Smartphone },
217+
{ hint: "client-device", btnLabel: t('common.platformPasskey'), Icon: FingerprintIcon },
218+
{ hint: "security-key", btnLabel: t('common.externalPasskey'), Icon: UsbStickDotIcon },
219+
{ hint: "hybrid", btnLabel: t('common.hybridPasskey'), Icon: SmartphoneNfcIcon },
219220
].map(({ Icon, hint, btnLabel }) => (
220221
<Button
221222
key={hint}

src/vite-env.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ interface ImportMetaEnv {
2727
readonly VITE_OPENID4VCI_MAX_ACCEPTED_BATCH_SIZE: string;
2828
readonly VITE_OPENID4VCI_TRANSACTION_ID_POLLING_INTERVAL_IN_SECONDS: string;
2929
readonly VITE_OPENID4VCI_TRANSACTION_ID_LIFETIME_IN_SECONDS: string;
30+
31+
readonly BRANDING_LOGO_LIGHT: string;
32+
readonly BRANDING_LOGO_DARK: string;
3033
}
3134

3235
interface ImportMeta {

vite-plugins/manifest.ts

Lines changed: 76 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,68 @@
11
import fs from 'fs';
2-
import { copyFile, open } from 'fs/promises';
2+
import { copyFile } from 'fs/promises';
33
import path from 'path';
44
import sharp from 'sharp';
5+
import { Plugin } from 'vite';
56
import type { ManifestOptions } from 'vite-plugin-pwa';
67

7-
function findBrandingFile(filePath: string): string|null {
8+
type BrandingFile = {
9+
readonly pathname: string;
10+
readonly filename: string;
11+
readonly isDefault: boolean;
12+
readonly isCustom: boolean;
13+
}
14+
15+
function findBrandingFile(filePathInput: string): BrandingFile | null {
816
const customFilePath = path.join(
9-
path.dirname(filePath), "custom", path.basename(filePath)
17+
path.dirname(filePathInput), "custom", path.basename(filePathInput)
1018
);
1119

12-
if (fs.existsSync(customFilePath)) return customFilePath;
13-
if (fs.existsSync(filePath)) return filePath;
14-
return null
20+
const hasDefault = fs.existsSync(filePathInput);
21+
const hasCustom = fs.existsSync(customFilePath);
22+
23+
if (!hasDefault && !hasCustom) {
24+
return null;
25+
}
26+
27+
const pathname = hasCustom ? customFilePath : filePathInput;
28+
const filename = path.basename(pathname);
29+
30+
return {
31+
pathname,
32+
filename,
33+
isDefault: hasDefault && !hasCustom,
34+
isCustom: hasCustom,
35+
}
1536
}
1637

17-
function findLogoFile(baseDir: string, name: string): string|null {
18-
const svgPath = findBrandingFile(path.join(baseDir, `${name}.svg`));
19-
const pngPath = findBrandingFile(path.join(baseDir, `${name}.png`));
38+
function findLogoFile(baseDir: string, name: string): BrandingFile|null {
39+
const svgFile = findBrandingFile(path.join(baseDir, `${name}.svg`));
40+
const pngFile = findBrandingFile(path.join(baseDir, `${name}.png`));
2041

21-
if (svgPath) return svgPath;
22-
if (pngPath) return pngPath;
42+
if (svgFile?.isCustom) return svgFile;
43+
if (pngFile?.isCustom) return pngFile;
44+
if (svgFile?.isDefault) return svgFile;
45+
if (pngFile?.isDefault) return pngFile;
2346
return null;
2447
}
2548

49+
type LogoFiles = Record<`logo_${'light'|'dark'}`, BrandingFile>;
50+
51+
function findLogoFiles(sourceDir: string): LogoFiles {
52+
const files: Partial<LogoFiles> = {};
53+
54+
for (const logoId of ["logo_light", "logo_dark"] as const) {
55+
const logoFile = findLogoFile(sourceDir, logoId);
56+
if (!logoFile) {
57+
throw new Error(`${logoId} not found`);
58+
}
59+
60+
files[logoId] = logoFile
61+
}
62+
63+
return files as LogoFiles;
64+
}
65+
2666
type GenerateAllIconsOptions = {
2767
sourceDir: string;
2868
publicDir: string;
@@ -38,28 +78,23 @@ async function generateAllIcons({
3878
appleTouchIcon,
3979
manifestIconSizes,
4080
}: GenerateAllIconsOptions): Promise<ManifestOptions['icons']> {
41-
const faviconPath = findBrandingFile(path.join(sourceDir, "favicon.ico"));
42-
if (!faviconPath) {
81+
const favicon = findBrandingFile(path.join(sourceDir, "favicon.ico"));
82+
if (!favicon) {
4383
throw new Error("favicon not found");
4484
}
4585

46-
const logoLightPath = findLogoFile(sourceDir, 'logo_light');
47-
if (!logoLightPath) {
48-
throw new Error("light mode logo not found");
49-
}
50-
51-
const logoDarkPath = findLogoFile(sourceDir, 'logo_dark');
52-
if (!logoDarkPath) {
53-
throw new Error("dark mode logo not found");
54-
}
86+
const {
87+
logo_light: logoLight,
88+
logo_dark: logoDark,
89+
} = findLogoFiles(sourceDir);
5590

56-
// Full scale svgs
91+
// Full scale logos
5792
if (copySource !== false) {
5893
await Promise.all([
59-
copyFile(logoLightPath, path.join(publicDir, path.basename(logoLightPath))),
60-
copyFile(logoDarkPath, path.join(publicDir, path.basename(logoDarkPath))),
61-
copyFile(faviconPath, path.join(publicDir, path.basename(faviconPath))).catch(() => {
62-
console.warn(`No file ${faviconPath} was found, skipping...`);
94+
copyFile(logoLight.pathname, path.join(publicDir, logoLight.filename)),
95+
copyFile(logoDark.pathname, path.join(publicDir, logoDark.filename)),
96+
copyFile(favicon.pathname, path.join(publicDir, path.basename(favicon.filename))).catch(() => {
97+
console.warn(`No file ${favicon} was found, skipping...`);
6398
}),
6499
]);
65100
}
@@ -72,7 +107,7 @@ async function generateAllIcons({
72107

73108
// Apple touch icon
74109
if (appleTouchIcon !== false) {
75-
sharp(logoDarkPath)
110+
sharp(logoDark.pathname)
76111
.png()
77112
.resize(180, 180)
78113
.toFile(path.join(iconsDir, 'apple-touch-icon.png'));
@@ -81,7 +116,7 @@ async function generateAllIcons({
81116
// Manifest icons
82117
const icons: ManifestOptions['icons'] = [];
83118

84-
const manifestIcon = sharp(logoDarkPath).png()
119+
const manifestIcon = sharp(logoDark.pathname).png()
85120

86121
for (const size of manifestIconSizes) {
87122
const sizeStr = `${size}x${size}`;
@@ -152,10 +187,21 @@ export async function generateManifest(env: Record<string, string|null|undefined
152187
};
153188
}
154189

155-
export function ManifestPlugin(env) {
190+
export function ManifestPlugin(env): Plugin {
156191
return {
157192
name: 'manifest-plugin',
158193

194+
config() {
195+
const { logo_light, logo_dark } = findLogoFiles(path.resolve('branding'));
196+
197+
return {
198+
define: {
199+
"import.meta.env.BRANDING_LOGO_LIGHT": JSON.stringify(`/${logo_light.filename}`),
200+
"import.meta.env.BRANDING_LOGO_DARK": JSON.stringify(`/${logo_dark.filename}`),
201+
}
202+
}
203+
},
204+
159205
async configureServer(server) {
160206
// For dev
161207
const manifestPath = path.resolve("public/manifest.json");

0 commit comments

Comments
 (0)