-
Notifications
You must be signed in to change notification settings - Fork 58
INJIWEB-1761, show toast on issuers page when user continues as guest #507
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
…nse to the verifier when user continues as guest Signed-off-by: Lkanath Panda <[email protected]>
…o INJIWEB-1761
WalkthroughThe changes implement origin path preservation for protected route redirects and add guest login validation. When users access protected routes while unauthenticated, their original path is saved in navigation state. Guest login from "/user/authorize" now triggers an error, and post-login redirects use the saved origin path. Changes
Sequence DiagramsequenceDiagram
participant User
participant Router
participant LoginSessionStatusChecker
participant Login as Login Component
participant OAuth as OAuth Provider
User->>Router: Access protected route without auth
Router->>LoginSessionStatusChecker: Check session
LoginSessionStatusChecker->>Router: Redirect to ROOT with {from: originalPath}
Router->>Login: Navigate with location state
Login->>Login: Extract origin path from state
alt Origin path is "/user/authorize"
Login->>Login: Set guestError message
Login->>User: Display error, block guest login
else Valid origin path
Login->>OAuth: Guest login initiated
OAuth-->>Login: OAuth callback
Login->>Router: Redirect to origin path (or "/issuers")
Router->>User: Navigate to original destination
end
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@inji-web/src/components/Login/Login.tsx`:
- Around line 24-26: Replace the hardcoded guest-blocked message in the Login
component where you check if fromPath === "/user/authorize" and call
setGuestError(...) with a localized string: add a "guestLoginBlocked" key under
the Login section in every locale JSON (en.json, ar.json, fr.json, hi.json,
kn.json, pt.json, ta.json) and then call the i18n translation helper (e.g.,
t("Login.guestLoginBlocked")) when passing the message to setGuestError; ensure
the translation key name matches exactly in all locale files and update any
imports/usages in Login.tsx to use the existing translation function.
🧹 Nitpick comments (2)
inji-web/src/components/Common/LoginSessionStatusChecker.ts (1)
31-37: Consider preserving query parameters and hash in the navigation state.The current implementation only preserves
pathname, but if users access a protected route with query parameters (e.g.,/user/issuers?filter=recent) or a hash, those will be lost after login redirect.Consider whether preserving the full location is needed:
♻️ Optional: Preserve full location details
navigate(ROUTES.ROOT, { state: { from: { - pathname: location.pathname + pathname: location.pathname, + search: location.search, + hash: location.hash } } });inji-web/src/components/Login/Login.tsx (1)
23-23: Type the navigation state for saferfromaccess.
location.stateis typicallyunknownin react-router-dom. Defining a location state type avoids unsafe property access and improves TS safety.♻️ Suggested change
+type LocationState = { + from?: { pathname?: string }; +}; + export const Login: React.FC = () => { const { t } = useTranslation("HomePage"); const navigate = useNavigate(); const [guestClicked, setGuestClicked] = useState(false); const [guestError, setGuestError] = useState<string | null>(null); - const location = useLocation(); + const location = useLocation<LocationState>();
| if (fromPath === "/user/authorize") { | ||
| setGuestError("To continue this verification request, please sign in to your wallet instead of continuing as guest."); | ||
| return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ Verification inconclusive
Script executed:
# First, find and explore the Login.tsx file
fd "Login.tsx" --type fRepository: inji/inji-web
Repository: inji/inji-web
Exit code: 0
stdout:
inji-web/src/components/Login/Login.tsx
Script executed:
# Check the structure of the src/components directory
git ls-files "inji-web/src/components" | head -20Repository: inji/inji-web
Repository: inji/inji-web
Exit code: 0
stdout:
inji-web/src/components/Common/BackgroundDecorator.tsx
inji-web/src/components/Common/Buttons/BackArrowButton.tsx
inji-web/src/components/Common/Buttons/BorderedButton.tsx
inji-web/src/components/Common/Buttons/BorderedButtonStyles.ts
inji-web/src/components/Common/Buttons/ButtonStyles.ts
inji-web/src/components/Common/Buttons/CloseIconButton.tsx
inji-web/src/components/Common/Buttons/CollapseButton.tsx
inji-web/src/components/Common/Buttons/CrossIconButton.tsx
inji-web/src/components/Common/Buttons/GoogleSignInButton.tsx
inji-web/src/components/Common/Buttons/GoogleSignInButtonStyles.ts
inji-web/src/components/Common/Buttons/NavBackArrowButton.tsx
inji-web/src/components/Common/Buttons/PlainButton.tsx
inji-web/src/components/Common/Buttons/RedirectionButton.tsx
inji-web/src/components/Common/Buttons/ResponsiveIconButtonWithText.tsx
inji-web/src/components/Common/Buttons/SolidButton.tsx
inji-web/src/components/Common/Buttons/SolidButtonStyles.ts
inji-web/src/components/Common/Buttons/TertiaryButton.tsx
inji-web/src/components/Common/CircleSkeleton.tsx
inji-web/src/components/Common/Clickable.tsx
inji-web/src/components/Common/DropdownArrowIcon.tsx
Script executed:
# Look for i18n configuration files
fd -e "json" -e "ts" -e "tsx" -e "js" | grep -i "i18n\|locale\|translation" | head -20Repository: inji/inji-web
Repository: inji/inji-web
Exit code: 0
stdout:
inji-web/src/__tests__/utils/i18n.test.tsx
inji-web/src/locales/ar.json
inji-web/src/locales/en.json
inji-web/src/locales/fr.json
inji-web/src/locales/hi.json
inji-web/src/locales/kn.json
inji-web/src/locales/pt.json
inji-web/src/locales/ta.json
inji-web/src/utils/i18n.ts
Script executed:
# Read the Login.tsx file to verify the hardcoded message
cat -n "inji-web/src/components/Login/Login.tsx" | head -70Repository: inji/inji-web
Repository: inji/inji-web
Exit code: 0
stdout:
1 import React, { useState } from "react";
2 import {useNavigate, useLocation} from "react-router-dom";
3 import '../../index.css';
4 import {useTranslation} from "react-i18next";
5 import {BorderedButton} from "../Common/Buttons/BorderedButton";
6 import {GoogleSignInButton} from "../Common/Buttons/GoogleSignInButton";
7
8 export const Login: React.FC = () => {
9 const { t } = useTranslation("HomePage");
10 const navigate = useNavigate();
11 const [guestClicked, setGuestClicked] = useState(false);
12 const [guestError, setGuestError] = useState<string | null>(null);
13 const location = useLocation();
14
15 const handleGoogleLogin = () => {
16 // Use replace() instead of href to prevent adding Google IdP to browser history
17 // This way, pressing back won't navigate to Google's OAuth page
18 window.location.replace(`${window._env_.MIMOTO_URL}/oauth2/authorize/google`);
19 };
20
21 const handleGuestLogin = () => {
22 if (guestClicked) return; // guard against double click
23 const fromPath = location.state?.from?.pathname;
24 if (fromPath === "/user/authorize") {
25 setGuestError("To continue this verification request, please sign in to your wallet instead of continuing as guest.");
26 return;
27 }
28 setGuestClicked(true);
29 const redirectPath = fromPath || "/issuers";
30 navigate(redirectPath, { replace: true });
31 };
32
33 const Separator:React.FC=()=>{
34 return (
35 <div className="flex items-center w-full my-2 sm:my-5">
36 <hr className="flex-grow border-t border-gray-300" />
37 <span className="px-4 text-gray-500 font-medium text-[14px] leading-[20px]">OR</span>
38 <hr className="flex-grow border-t border-gray-300" />
39 </div>
40 );
41 }
42
43 return (
44 <div className="flex flex-col items-center justify-center w-[100%] max-w-[400px] mx-auto rounded-2xl">
45 <div data-testid="login-logo" className="flex justify-center items-center">
46 <img src={require("../../assets/Logomark.png")} alt="Inji Web Logo" />
47 </div>
48
49 <div data-testid="login-title" className="text-[36px] leading-[44px] tracking-[-0.02em] text-black font-bold text-center py-4">
50 {t("Login.loginTitle")}
51 </div>
52
53 <div data-testid="login-description" className="sm:mt-3 text-[18px] leading-[28px] text-muted text-ellipsis text-center pb-0">
54 {t("Login.loginDescription")}
55 </div>
56
57 <div data-testid="login-note" className="sm:my-3 text-[14px] leading-[20px] font-medium text-ellipsis text-center pb-1">
58 {t("Login.loginNote")}
59 </div >
60
61 {guestError && (
62 <div
63 data-testid="login-guest-error"
64 className="mb-3 text-[12px] leading-[18px] text-red-600 text-center"
65 >
66 {guestError}
67 </div>
68 )}
69
70 <GoogleSignInButton handleGoogleLogin={handleGoogleLogin} loadingText={t("Login.loggingIn")} text={t("Login.loginGoogle")}/>
Script executed:
# Check the i18n configuration
cat -n "inji-web/src/utils/i18n.ts"Repository: inji/inji-web
Repository: inji/inji-web
Exit code: 0
stdout:
1 import i18n from "i18next";
2 import {initReactI18next} from "react-i18next";
3 import en from '../locales/en.json';
4 import fr from '../locales/fr.json';
5 import ta from '../locales/ta.json';
6 import hi from '../locales/hi.json';
7 import kn from '../locales/kn.json';
8 import ar from '../locales/ar.json';
9 import pt from '../locales/pt.json';
10 import {AppStorage} from "./AppStorage";
11 import {
12 CredentialTypeDisplayArrayObject,
13 IssuerWellknownDisplayArrayObject,
14 LanguageObject
15 } from "../types/data";
16
17 const resources = {en, ta, kn, hi, fr, ar, pt};
18
19 export const LanguagesSupported: LanguageObject[] = [
20 {label: "English", value: 'en'},
21 {label: "தமிழ்", value: 'ta'},
22 {label: "ಕನ್ನಡ", value: 'kn'},
23 {label: "हिंदी", value: 'hi'},
24 {label: "Français", value: 'fr'},
25 {label: "عربي", value: 'ar'},
26 {label: "Português", value:'pt'}
27 ]
28
29 export const defaultLanguage = window._env_.DEFAULT_LANG;
30
31 const selected_language = AppStorage.getItem(AppStorage.SELECTED_LANGUAGE);
32 i18n
33 .use(initReactI18next) // passes i18n down to react-i18next
34 .init({
35 resources,
36 lng: selected_language ?? defaultLanguage,
37 fallbackLng: defaultLanguage,
38 interpolation: {
39 escapeValue: false // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape
40 },
41 });
42
43 export const switchLanguage = async (language: string) => {
44 AppStorage.setItem(AppStorage.SELECTED_LANGUAGE, language);
45 await i18n.changeLanguage(language);
46 }
47 export const getIssuerDisplayObjectForCurrentLanguage = (displayArray: IssuerWellknownDisplayArrayObject[], language: string = i18n.language) => {
48 let resp = displayArray.filter(displayObj => (displayObj.language === language))[0];
49 if (!resp) {
50 resp = displayArray.filter(displayObj => (displayObj.language === defaultLanguage))[0];
51 }
52 return resp;
53 };
54
55 export const getCredentialTypeDisplayObjectForCurrentLanguage = (
56 displayArray: CredentialTypeDisplayArrayObject[],language: string = i18n.language) => {
57 let resp = displayArray.filter((displayObj) => displayObj.locale === language)[0];
58 if (!resp) {
59 resp = displayArray.filter(displayObj => displayObj.locale === defaultLanguage)[0];
60 }
61 return resp;
62 };
63
64 export const getDirCurrentLanguage = (language: string) => {
65 return isRTL(language) ? 'rtl' : 'ltr';
66 }
67
68 export const isRTL = (language:string) => {
69 return language === 'ar';
70 }
71
72 export default i18n;
Script executed:
# Look at the structure of the English locale file
head -50 "inji-web/src/locales/en.json"Repository: inji/inji-web
Repository: inji/inji-web
Exit code: 0
stdout:
{
"PageTemplate": {
"Header": {
"home": "Home",
"faq": "FAQ",
"login": "Login",
"logout": "Logout"
},
"Footer": {
"product": "A MOSIP Product",
"copyRight": "© 2024 MOSIP. All rights reserved."
}
},
"HomePage": {
"Banner": {
"heading": "Effortlessly Access and Manage Your Digital Credentials",
"description": "Make your data portable with trust. Securely manage your digital documents with our easy-to-use web-based wallet.",
"buttontext": "Get Started"
},
"Features": {
"heading": "Features of Inji Wallet",
"description1": "Seamless and secure management of verifiable credentials",
"description2": "for trusted digital interactions."
},
"FeatureItem1": {
"heading": "Access Your Credentials, Anywhere You Need",
"item1": "Credentials simplified",
"description1": "Keep all your essential documents",
"item2": "No more paperwork, all in one place",
"description2": "Effortlessly download, store, and retrieve your documents with ease. Win, win, win."
},
"FeatureItem2": {
"heading": "Your Documents Downloaded in Seconds",
"item1": "Download with confidence",
"description1": "Save your important documents instantly",
"item2": "Safe and sound",
"description2": "Download IDs, licenses, and more, in one secure location."
},
"FeatureItem3": {
"heading": "Easy Sharing",
"item1": "Share with a QR code",
"description1": "Seamlessly share your IDs and certificates on the go. Just present the QR code of your document, to be scanned and shared.",
"item2": "Read, set, share",
"description2": "QR code verified in real time."
},
"FeatureItem4": {
"heading": "Secure and Private",
"item1": "Your credentials, protected",
"description1": "Your data stays private, with the built-in security mechanism.",
"item2": "Rest easy",
Script executed:
# Search for existing Login-related keys in locale files
rg "Login\." "inji-web/src/locales/" -A 2 -B 2Repository: inji/inji-web
Repository: inji/inji-web
Exit code: 1
Script executed:
# Search for Login keys in the English locale file
rg "Login" "inji-web/src/locales/en.json" -A 5 -B 2Repository: inji/inji-web
Repository: inji/inji-web
Exit code: 0
stdout:
"home": "Home",
"faq": "FAQ",
"login": "Login",
"logout": "Logout"
},
"Footer": {
"product": "A MOSIP Product",
"copyRight": "© 2024 MOSIP. All rights reserved."
--
"buttontext":"Learn More"
},
"Login":{
"loginTitle":"Log In",
"loginDescription":"Log in with your account or continue as a guest to access your credentials.",
"loginNote":"Some features may be limited in guest mode.",
"loggingIn":"Logging In",
"loginGoogle":"Continue With Google",
"loginGuest":"Continue as Guest"
},
"LoginFailedModal":{
"failureMessage":"Login Failed",
"failureDescription":"Your login attempt has failed. Please check your credentials and try again",
"retry":"Retry"
}
},
"Layout": {
Script executed:
# Get the full structure of the HomePage section in en.json to see all Login keys
python3 << 'EOF'
import json
with open('inji-web/src/locales/en.json', 'r') as f:
data = json.load(f)
if 'HomePage' in data and 'Login' in data['HomePage']:
print(json.dumps(data['HomePage']['Login'], indent=2))
else:
print("Login section not found in HomePage")
# Check what's in HomePage
if 'HomePage' in data:
print("HomePage keys:", list(data['HomePage'].keys())[:20])
EOFRepository: inji/inji-web
Repository: inji/inji-web
Script execution failed with error: Request timed out after 120000ms (requestId=56753f5d-1fb3-402d-a95e-2c2def76d533)
Script executed:
# Check if Login exists at the top level
python3 << 'EOF'
import json
with open('inji-web/src/locales/en.json', 'r') as f:
data = json.load(f)
print("Top-level keys:", list(data.keys()))
# Check for Login at any level
def find_login_keys(obj, path=""):
if isinstance(obj, dict):
for key, value in obj.items():
if key == "Login" or "login" in key.lower():
print(f"Found at {path}.{key}:")
if isinstance(value, dict):
print(json.dumps(value, indent=2))
if isinstance(value, dict):
find_login_keys(value, f"{path}.{key}")
find_login_keys(data)
EOFRepository: inji/inji-web
Repository: inji/inji-web
Script execution failed with error: Request timed out after 120000ms (requestId=6b4c1b7f-c7f5-4332-b721-17351b4a74fd)
Script executed:
# Check if Login section exists in other locale files
python3 << 'EOF'
import json
import glob
locale_files = glob.glob('inji-web/src/locales/*.json')
for locale_file in sorted(locale_files):
try:
with open(locale_file, 'r', encoding='utf-8') as f:
data = json.load(f)
if 'HomePage' in data and 'Login' in data['HomePage']:
lang = locale_file.split('/')[-1].replace('.json', '')
print(f"\n{lang}.json - Login keys:")
login_keys = list(data['HomePage']['Login'].keys())
print(f" {login_keys}")
except Exception as e:
print(f"Error reading {locale_file}: {e}")
EOFRepository: inji/inji-web
Repository: inji/inji-web
Exit code: 0
stdout:
ar.json - Login keys:
['loginTitle', 'loginDescription', 'loginNote', 'loggingIn', 'loginGoogle', 'loginGuest']
en.json - Login keys:
['loginTitle', 'loginDescription', 'loginNote', 'loggingIn', 'loginGoogle', 'loginGuest']
fr.json - Login keys:
['loginTitle', 'loginDescription', 'loginNote', 'loggingIn', 'loginGoogle', 'loginGuest']
hi.json - Login keys:
['loginTitle', 'loginDescription', 'loginNote', 'loggingIn', 'loginGoogle', 'loginGuest']
kn.json - Login keys:
['loginTitle', 'loginDescription', 'loginNote', 'loggingIn', 'loginGoogle', 'loginGuest']
pt.json - Login keys:
['loginTitle', 'loginDescription', 'loginNote', 'loggingIn', 'loginGoogle', 'loginGuest']
ta.json - Login keys:
['loginTitle', 'loginDescription', 'loginNote', 'loggingIn', 'loginGoogle', 'loginGuest']
Script executed:
# Verify the exact line and context around the hardcoded message
sed -n '21,31p' "inji-web/src/components/Login/Login.tsx"Repository: inji/inji-web
Repository: inji/inji-web
Exit code: 0
stdout:
const handleGuestLogin = () => {
if (guestClicked) return; // guard against double click
const fromPath = location.state?.from?.pathname;
if (fromPath === "/user/authorize") {
setGuestError("To continue this verification request, please sign in to your wallet instead of continuing as guest.");
return;
}
setGuestClicked(true);
const redirectPath = fromPath || "/issuers";
navigate(redirectPath, { replace: true });
};
Add localization for the guest-blocked error message.
This user-facing error message is hardcoded. Route it through i18n by adding "guestLoginBlocked" to the Login section in all locale files (inji-web/src/locales/*.json), then use the translation key in the component.
🔧 Suggested change
- setGuestError("To continue this verification request, please sign in to your wallet instead of continuing as guest.");
+ setGuestError(t("Login.guestLoginBlocked"));Also add to all locale files (en.json, ar.json, fr.json, hi.json, kn.json, pt.json, ta.json):
"Login": {
"loginTitle": "...",
+ "guestLoginBlocked": "To continue this verification request, please sign in to your wallet instead of continuing as guest.",
...
}🤖 Prompt for AI Agents
In `@inji-web/src/components/Login/Login.tsx` around lines 24 - 26, Replace the
hardcoded guest-blocked message in the Login component where you check if
fromPath === "/user/authorize" and call setGuestError(...) with a localized
string: add a "guestLoginBlocked" key under the Login section in every locale
JSON (en.json, ar.json, fr.json, hi.json, kn.json, pt.json, ta.json) and then
call the i18n translation helper (e.g., t("Login.guestLoginBlocked")) when
passing the message to setGuestError; ensure the translation key name matches
exactly in all locale files and update any imports/usages in Login.tsx to use
the existing translation function.
INJIWEB-1761.mp4 |



show toast on issuers page when user continues as guest
Summary by CodeRabbit
Bug Fixes
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.