-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/razorpay integration #17
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
Changes from all commits
0895df2
aefe23e
afe326d
4b1120b
cc95485
585fd01
2d6be1d
3a0a2d7
c4f90c8
962eb23
7b29a10
517a1f8
d9bbd3c
eee24c6
720bd6a
b781540
8d79585
578dd10
9276a54
4725f1a
a0d5b8f
f0e8f59
165382b
6dbb29f
e61b9a5
1a4fffc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,36 +1,15 @@ | ||
| # Node Environment | ||
| NODE_ENV=development | ||
|
|
||
| # Server Configuration | ||
| PORT=3000 | ||
| HOST=localhost | ||
|
|
||
| # Database (PostgreSQL) | ||
| DATABASE_URL=postgresql://devradar:devradar@localhost:5432/devradar?schema=public | ||
|
|
||
| # Redis | ||
| REDIS_URL=redis://localhost:6379 | ||
|
|
||
| # GitHub OAuth | ||
| GITHUB_CLIENT_ID=your_github_client_id | ||
| GITHUB_CLIENT_SECRET=your_github_client_secret | ||
| GITHUB_CALLBACK_URL=http://localhost:3000/auth/callback | ||
| # GitHub Webhooks (for Boss Battles - achievements from GitHub events) | ||
| # Generate with: openssl rand -hex 32 | ||
| GITHUB_WEBHOOK_SECRET=your_webhook_secret_for_github | ||
|
|
||
| # JWT | ||
| JWT_SECRET=your_super_secret_jwt_key_change_in_production | ||
| JWT_EXPIRES_IN=7d | ||
|
|
||
| # WebSocket | ||
| WS_PORT=3001 | ||
|
|
||
| # Logging | ||
| LOG_LEVEL=debug | ||
|
|
||
| # Slack Integration (Phase 3 - Optional) | ||
| # Create a Slack App at https://api.slack.com/apps | ||
| SLACK_CLIENT_ID= | ||
| SLACK_CLIENT_SECRET= | ||
| SLACK_SIGNING_SECRET= | ||
| # ============================================ | ||
| # DevRadar Environment Configuration | ||
| # ============================================ | ||
|
|
||
| # Environment variables are now organized per application: | ||
| # | ||
| # - Server variables: apps/server/.env.example | ||
| # - Web variables: apps/web/.env.example | ||
| # | ||
| # Copy the appropriate .env.example to .env in each app directory | ||
| # and fill in your values before running locally. | ||
| # | ||
| # For production deployment: | ||
| # - Vercel: Add NEXT_PUBLIC_RAZORPAY_KEY_ID in Vercel dashboard | ||
| # - Koyeb: Add all vars from apps/server/.env.example in Koyeb dashboard |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,4 +47,6 @@ coverage/ | |
| docker-compose.override.yml | ||
| apps/server/src/generated/ | ||
|
|
||
| rules/ | ||
| rules/ | ||
| docs/ | ||
| opencode.jsonc | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| # ============================================ | ||
| # DevRadar VS Code Extension Environment Configuration | ||
| # ============================================ | ||
| # Copy this file to .env and fill in your values | ||
|
|
||
| # ----------------------------------------------------------------------------- | ||
| # DEPLOYMENT: Add these in VS Code Extension settings or .env file | ||
| # ----------------------------------------------------------------------------- | ||
|
|
||
| # Development Server URLs (for local development) | ||
| DEV_SERVER_URL=http://localhost:3000 | ||
| DEV_WS_URL=ws://localhost:3000/ws | ||
|
|
||
| # Production Server URLs (for Koyeb deployment) | ||
| # Replace with your Koyeb app URL after deployment | ||
| PROD_SERVER_URL=https://your-koyeb-app.koyeb.app | ||
| PROD_WS_URL=wss://your-koyeb-app.koyeb.app/ws | ||
|
|
||
| # Web Application URL (for links from extension to web app) | ||
| # Update this to your Vercel deployment URL in production | ||
| NEXT_PUBLIC_WEB_APP_URL=http://localhost:3000 | ||
|
|
||
| # ----------------------------------------------------------------------------- | ||
| # PRODUCTION VALUES (update these when deploying) | ||
| # ----------------------------------------------------------------------------- | ||
| # NEXT_PUBLIC_WEB_APP_URL=https://devradar.io | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ import * as vscode from 'vscode'; | |
|
|
||
| import { ActivityTracker } from './services/activityTracker'; | ||
| import { AuthService } from './services/authService'; | ||
| import { FeatureGatingService } from './services/featureGatingService'; | ||
| import { FriendRequestService } from './services/friendRequestService'; | ||
| import { WebSocketClient } from './services/wsClient'; | ||
| import { ConfigManager } from './utils/configManager'; | ||
|
|
@@ -48,6 +49,8 @@ class DevRadarExtension implements vscode.Disposable { | |
| private readonly statsProvider: StatsProvider; | ||
| private readonly leaderboardProvider: LeaderboardProvider; | ||
| private statsRefreshInterval: NodeJS.Timeout | null = null; | ||
| // Phase 5: Feature Gating | ||
| private readonly featureGatingService: FeatureGatingService; | ||
|
|
||
| constructor(context: vscode.ExtensionContext) { | ||
| this.logger = new Logger('DevRadar'); | ||
|
|
@@ -77,6 +80,12 @@ class DevRadarExtension implements vscode.Disposable { | |
| // Phase 2: Gamification views | ||
| this.statsProvider = new StatsProvider(this.logger); | ||
| this.leaderboardProvider = new LeaderboardProvider(this.logger); | ||
| // Phase 5: Feature Gating | ||
| this.featureGatingService = new FeatureGatingService( | ||
| this.authService, | ||
| this.logger, | ||
| this.configManager | ||
| ); | ||
| /* Track disposables */ | ||
| this.disposables.push( | ||
| this.authService, | ||
|
|
@@ -89,7 +98,8 @@ class DevRadarExtension implements vscode.Disposable { | |
| this.statusBar, | ||
| this.configManager, | ||
| this.statsProvider, | ||
| this.leaderboardProvider | ||
| this.leaderboardProvider, | ||
| this.featureGatingService | ||
| ); | ||
| } | ||
|
|
||
|
|
@@ -194,6 +204,14 @@ class DevRadarExtension implements vscode.Disposable { | |
| void vscode.commands.executeCommand('devradar.friendRequests.focus'); | ||
| }, | ||
| }, | ||
| { | ||
| id: 'devradar.enableGhostMode', | ||
| handler: () => this.handleEnableGhostMode(), | ||
| }, | ||
| { | ||
| id: 'devradar.openBilling', | ||
| handler: () => this.handleOpenBilling(), | ||
| }, | ||
| ]; | ||
|
|
||
| for (const command of commands) { | ||
|
|
@@ -761,6 +779,34 @@ class DevRadarExtension implements vscode.Disposable { | |
| } | ||
| } | ||
|
|
||
| private async handleEnableGhostMode(): Promise<void> { | ||
| // Check if user has access to ghost mode feature | ||
| const hasAccess = await this.featureGatingService.promptUpgrade('ghostMode'); | ||
| if (!hasAccess) { | ||
| return; | ||
| } | ||
|
|
||
| // Toggle ghost mode | ||
| const currentMode = this.configManager.get('privacyMode'); | ||
| await this.configManager.update('privacyMode', !currentMode); | ||
|
|
||
| const message = !currentMode | ||
| ? 'DevRadar: Ghost mode enabled - you are now invisible to others' | ||
| : 'DevRadar: Ghost mode disabled - your activity is now visible'; | ||
|
|
||
| void vscode.window.showInformationMessage(message); | ||
| this.activityTracker.sendStatusUpdate(); | ||
| } | ||
|
Comment on lines
+782
to
+799
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
rg -n "privacyMode|ghostMode" --type=ts -C2 apps/extension/Repository: senutpal/devRadar Length of output: 5655 Use a separate config key for The
Either create a separate 🤖 Prompt for AI Agents |
||
|
|
||
| private async handleOpenBilling(): Promise<void> { | ||
| const webAppUrl = this.featureGatingService.getWebAppUrl(); | ||
| const tier = this.featureGatingService.getCurrentTier(); | ||
| const billingUrl = `${webAppUrl}/dashboard/billing?current=${tier}`; | ||
|
|
||
| await vscode.env.openExternal(vscode.Uri.parse(billingUrl)); | ||
| this.logger.info('Opened billing page', { currentTier: tier }); | ||
| } | ||
|
|
||
| dispose(): void { | ||
| this.logger.info('Disposing DevRadar extension...'); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,143 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Feature Gating Service | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Client-side feature access control for the VS Code extension. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Checks user tier and prompts for upgrade when accessing gated features. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type Feature, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type SubscriptionTier, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SUBSCRIPTION_FEATURES, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FEATURE_DESCRIPTIONS, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from '@devradar/shared'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as vscode from 'vscode'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { AuthService } from './authService'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { ConfigManager } from '../utils/configManager'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { Logger } from '../utils/logger'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const FEATURE_TIER_MAP: Record<Feature, SubscriptionTier> = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| presence: 'FREE', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| friends: 'FREE', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| globalLeaderboard: 'FREE', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| friendsLeaderboard: 'FREE', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| streaks: 'FREE', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| achievements: 'FREE', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| poke: 'FREE', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| privacyMode: 'FREE', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| unlimitedFriends: 'PRO', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ghostMode: 'PRO', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| customStatus: 'PRO', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| history30d: 'PRO', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| themes: 'PRO', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| customEmoji: 'PRO', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prioritySupport: 'PRO', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| conflictRadar: 'TEAM', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| teamCreation: 'TEAM', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| teamAnalytics: 'TEAM', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| slackIntegration: 'TEAM', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| privateLeaderboards: 'TEAM', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| adminControls: 'TEAM', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ssoSaml: 'TEAM', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dedicatedSupport: 'TEAM', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } as const; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+20
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider deriving This map duplicates tier information already present in ♻️ Optional: Derive from SUBSCRIPTION_FEATURESimport {
type Feature,
type SubscriptionTier,
SUBSCRIPTION_FEATURES,
FEATURE_DESCRIPTIONS,
} from '@devradar/shared';
const TIER_PRIORITY: SubscriptionTier[] = ['FREE', 'PRO', 'TEAM'];
const FEATURE_TIER_MAP: Record<Feature, SubscriptionTier> = Object.fromEntries(
TIER_PRIORITY.flatMap((tier) =>
SUBSCRIPTION_FEATURES[tier].map((feature) => [feature, tier] as const)
).reduce((acc, [feature, tier]) => {
// Keep the lowest tier (first occurrence)
if (!acc.has(feature)) acc.set(feature, tier);
return acc;
}, new Map<Feature, SubscriptionTier>())
) as Record<Feature, SubscriptionTier>;🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** Manages feature access control and upgrade prompts. */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export class FeatureGatingService implements vscode.Disposable { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| constructor( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private readonly authService: AuthService, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private readonly logger: Logger, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private readonly configManager: ConfigManager | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Checks if the current user has access to a feature. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param feature - The feature to check access for | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @returns true if the user has access | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hasAccess(feature: Feature): boolean { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const user = this.authService.getUser(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!user) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Defensive: ensure tier is valid, default to FREE if not | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime value might differ from type | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const tier = (user.tier ?? 'FREE') as SubscriptionTier; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return SUBSCRIPTION_FEATURES[tier].includes(feature); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+59
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Add validation for unexpected tier values. The defensive ♻️ Suggested defensive approach hasAccess(feature: Feature): boolean {
const user = this.authService.getUser();
if (!user) {
return false;
}
- // Defensive: ensure tier is valid, default to FREE if not
- // eslint-disable-next-line `@typescript-eslint/no-unnecessary-condition` -- runtime value might differ from type
- const tier = (user.tier ?? 'FREE') as SubscriptionTier;
- return SUBSCRIPTION_FEATURES[tier].includes(feature);
+ // Defensive: ensure tier is valid and exists in SUBSCRIPTION_FEATURES
+ const tier = user.tier as SubscriptionTier | undefined;
+ const features = SUBSCRIPTION_FEATURES[tier ?? 'FREE'];
+ return features?.includes(feature) ?? false;
}🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Gets the minimum tier required for a feature. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param feature - The feature to check | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @returns The minimum tier required | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getRequiredTier(feature: Feature): SubscriptionTier { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return FEATURE_TIER_MAP[feature]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Gets the user's current tier. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @returns The user's tier or 'FREE' if not authenticated | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getCurrentTier(): SubscriptionTier { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const user = this.authService.getUser(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return (user?.tier ?? 'FREE') as SubscriptionTier; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Prompts the user to upgrade if they don't have access to a feature. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Opens the billing page in the browser with upgrade parameters. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param feature - The feature requiring upgrade | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @returns true if the user has access, false if they need to upgrade | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async promptUpgrade(feature: Feature): Promise<boolean> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (this.hasAccess(feature)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const requiredTier = this.getRequiredTier(feature); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const featureDescription = FEATURE_DESCRIPTIONS[feature]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const action = await vscode.window.showWarningMessage( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `DevRadar: "${featureDescription}" requires ${requiredTier} tier.`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Upgrade Now', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Maybe Later' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (action === 'Upgrade Now') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const webAppUrl = this.getWebAppUrl(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const upgradeUrl = `${webAppUrl}/dashboard/billing?upgrade=${requiredTier}&feature=${feature}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await vscode.env.openExternal(vscode.Uri.parse(upgradeUrl)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.logger.info('Opened upgrade page', { feature, requiredTier }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+96
to
+119
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider URL-encoding query parameters. While current ♻️ Optional fix if (action === 'Upgrade Now') {
const webAppUrl = this.getWebAppUrl();
- const upgradeUrl = `${webAppUrl}/dashboard/billing?upgrade=${requiredTier}&feature=${feature}`;
+ const upgradeUrl = `${webAppUrl}/dashboard/billing?upgrade=${encodeURIComponent(requiredTier)}&feature=${encodeURIComponent(feature)}`;
await vscode.env.openExternal(vscode.Uri.parse(upgradeUrl));📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Gets the upgrade URL for a specific tier. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param tier - The target tier | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @returns The full upgrade URL | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getUpgradeUrl(tier: SubscriptionTier): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const webAppUrl = this.getWebAppUrl(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return `${webAppUrl}/dashboard/billing?upgrade=${tier}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Gets the web app URL from config or uses default. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @returns The web application URL | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getWebAppUrl(): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- config.get() can return undefined | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return this.configManager.get('webAppUrl') ?? 'http://localhost:3000'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
135
to
138
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove unnecessary nullish coalescing to fix pipeline failure.
🔧 Proposed fix getWebAppUrl(): string {
- return this.configManager.get('webAppUrl') ?? 'http://localhost:3000';
+ return this.configManager.get('webAppUrl');
}📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Actions: CI[error] 136-136: ESLint: '@typescript-eslint/no-unnecessary-condition' - Unnecessary conditional, expected left-hand side of '??' operator to be possibly null or undefined 🪛 GitHub Check: Build and Test[failure] 136-136: 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dispose(): void { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // No disposables to clean up | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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.
🧹 Nitpick | 🔵 Trivial
Consider renaming
NEXT_PUBLIC_WEB_APP_URLfor clarity.The
NEXT_PUBLIC_prefix is a Next.js convention for exposing environment variables to the browser. In a VS Code extension context, this naming is misleading. Consider using a more generic name likeWEB_APP_URLorDEVRADAR_WEB_URL.♻️ Suggested rename
📝 Committable suggestion
🤖 Prompt for AI Agents